mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-06 20:13:59 -05:00
Remove most of the remaining geth code and set up bazel (#235)
* Remove most of the remaining geth code and set up bazel for this * chmod +x * Add flake check * better flake detection Former-commit-id: 5c332ecbf2923943f646f1fe40befa95be883329 [formerly 99590fc354514584700e5ce8d7d30a8a7d541f29] Former-commit-id: e5f919b553fe698e98090965d34eb721990b5693
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
**/.git
|
||||
.git
|
||||
!.git/HEAD
|
||||
!.git/refs/heads
|
||||
**/*_test.go
|
||||
|
||||
build/_workspace
|
||||
build/_bin
|
||||
tests/testdata
|
||||
49
.gitignore
vendored
49
.gitignore
vendored
@@ -1,47 +1,2 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
/tmp
|
||||
*/**/*un~
|
||||
*/**/*.test
|
||||
*un~
|
||||
.DS_Store
|
||||
*/**/.DS_Store
|
||||
.ethtest
|
||||
*/**/*tx_database*
|
||||
*/**/*dapps*
|
||||
build/_vendor/pkg
|
||||
|
||||
#*
|
||||
.#*
|
||||
*#
|
||||
*~
|
||||
.project
|
||||
.settings
|
||||
|
||||
# used by the Makefile
|
||||
/build/_workspace/
|
||||
/build/bin/
|
||||
/geth*.zip
|
||||
|
||||
# travis
|
||||
profile.tmp
|
||||
profile.cov
|
||||
|
||||
# IdeaIDE
|
||||
.idea
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
|
||||
# dashboard
|
||||
/dashboard/assets/flow-typed
|
||||
/dashboard/assets/node_modules
|
||||
/dashboard/assets/stats.json
|
||||
/dashboard/assets/bundle.js
|
||||
/dashboard/assets/package-lock.json
|
||||
|
||||
**/yarn-error.log
|
||||
# Ignore bazel directories
|
||||
bazel-*
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "tests"]
|
||||
path = tests/testdata
|
||||
url = https://github.com/ethereum/tests
|
||||
123
.mailmap
123
.mailmap
@@ -1,123 +0,0 @@
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
|
||||
|
||||
Viktor Trón <viktor.tron@gmail.com>
|
||||
|
||||
Joseph Goulden <joegoulden@gmail.com>
|
||||
|
||||
Nick Savers <nicksavers@gmail.com>
|
||||
|
||||
Maran Hidskes <maran.hidskes@gmail.com>
|
||||
|
||||
Taylor Gerring <taylor.gerring@gmail.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com> <taylor.gerring@ethereum.org>
|
||||
|
||||
Bas van Kervel <bas@ethdev.com>
|
||||
Bas van Kervel <bas@ethdev.com> <basvankervel@ziggo.nl>
|
||||
Bas van Kervel <bas@ethdev.com> <basvankervel@gmail.com>
|
||||
Bas van Kervel <bas@ethdev.com> <bas-vk@users.noreply.github.com>
|
||||
|
||||
Sven Ehlert <sven@ethdev.com>
|
||||
|
||||
Vitalik Buterin <v@buterin.com>
|
||||
|
||||
Marian Oancea <contact@siteshop.ro>
|
||||
|
||||
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||
|
||||
Heiko Hees <heiko@heiko.org>
|
||||
|
||||
Alex Leverington <alex@ethdev.com>
|
||||
Alex Leverington <alex@ethdev.com> <subtly@users.noreply.github.com>
|
||||
|
||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||
|
||||
Gavin Wood <i@gavwood.com>
|
||||
|
||||
Martin Becze <mjbecze@gmail.com>
|
||||
Martin Becze <mjbecze@gmail.com> <wanderer@users.noreply.github.com>
|
||||
|
||||
Dimitry Khokhlov <winsvega@mail.ru>
|
||||
|
||||
Roman Mandeleil <roman.mandeleil@gmail.com>
|
||||
|
||||
Alec Perseghin <aperseghin@gmail.com>
|
||||
|
||||
Alon Muroch <alonmuroch@gmail.com>
|
||||
|
||||
Arkadiy Paronyan <arkadiy@ethdev.com>
|
||||
|
||||
Jae Kwon <jkwon.work@gmail.com>
|
||||
|
||||
Aaron Kumavis <kumavis@users.noreply.github.com>
|
||||
|
||||
Nick Dodson <silentcicero@outlook.com>
|
||||
|
||||
Jason Carver <jacarver@linkedin.com>
|
||||
Jason Carver <jacarver@linkedin.com> <ut96caarrs@snkmail.com>
|
||||
|
||||
Joseph Chow <ethereum@outlook.com>
|
||||
Joseph Chow <ethereum@outlook.com> ethers <TODO>
|
||||
|
||||
Enrique Fynn <enriquefynn@gmail.com>
|
||||
|
||||
Vincent G <caktux@gmail.com>
|
||||
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
|
||||
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
|
||||
Aron Fischer <github@aron.guru> <homotopycolimit@users.noreply.github.com>
|
||||
|
||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||
|
||||
Ville Sundell <github@solarius.fi>
|
||||
|
||||
Elliot Shepherd <elliot@identitii.com>
|
||||
|
||||
Yohann Léon <sybiload@gmail.com>
|
||||
|
||||
Gregg Dourgarian <greggd@tempworks.com>
|
||||
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
|
||||
Jens Agerberg <github@agerberg.me>
|
||||
|
||||
Nick Johnson <arachnid@notdot.net>
|
||||
|
||||
Henning Diedrich <hd@eonblast.com>
|
||||
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
|
||||
|
||||
Felix Lange <fjl@twurst.com>
|
||||
Felix Lange <fjl@twurst.com> <fjl@users.noreply.github.com>
|
||||
|
||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||
|
||||
Louis Holbrook <dev@holbrook.no>
|
||||
Louis Holbrook <dev@holbrook.no> <nolash@users.noreply.github.com>
|
||||
|
||||
Thomas Bocek <tom@tomp2p.net>
|
||||
|
||||
Victor Tran <vu.tran54@gmail.com>
|
||||
|
||||
Justin Drake <drakefjustin@gmail.com>
|
||||
|
||||
Frank Wang <eternnoir@gmail.com>
|
||||
|
||||
Gary Rong <garyrong0905@gmail.com>
|
||||
|
||||
Guillaume Nicolas <guin56@gmail.com>
|
||||
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com> <sorin@users.noreply.github.com>
|
||||
|
||||
Valentin Wüstholz <wuestholz@gmail.com>
|
||||
Valentin Wüstholz <wuestholz@gmail.com> <wuestholz@users.noreply.github.com>
|
||||
|
||||
Armin Braun <me@obrown.io>
|
||||
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com> <ernestodeltoro@users.noreply.github.com>
|
||||
15
.travis-bazelrc
Normal file
15
.travis-bazelrc
Normal file
@@ -0,0 +1,15 @@
|
||||
# TODO: Set up remote caching.
|
||||
|
||||
startup --host_jvm_args=-Xmx500m --host_jvm_args=-Xms500m
|
||||
|
||||
# Disable sandboxing since it may fail inside of Travis' containers because the
|
||||
# mount system call is not permitted.
|
||||
build --spawn_strategy=standalone --genrule_strategy=standalone
|
||||
|
||||
# Set some build options for travis container.
|
||||
build --local_resources=1536,1.5,0.5
|
||||
build --noshow_progress
|
||||
build --verbose_failures
|
||||
build --sandbox_debug
|
||||
build --test_output=errors
|
||||
build --flaky_test_attempts=5
|
||||
242
.travis.yml
242
.travis.yml
@@ -1,201 +1,61 @@
|
||||
language: go
|
||||
go_import_path: github.com/ethereum/go-ethereum
|
||||
go_import_path: github.com/prysmaticlabs/geth-sharding
|
||||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
#- os: linux
|
||||
# dist: trusty
|
||||
# sudo: required
|
||||
# go: 1.9.x
|
||||
# script:
|
||||
# - sudo modprobe fuse
|
||||
# - sudo chmod 666 /dev/fuse
|
||||
# - sudo chown root:$USER /etc/fuse.conf
|
||||
# - go run build/ci.go install
|
||||
# - go run build/ci.go test -coverage
|
||||
|
||||
# These are the latest Go versions.
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
go: 1.10.x
|
||||
script:
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||
|
||||
#- os: osx
|
||||
# go: "1.10"
|
||||
# script:
|
||||
# - unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
||||
# - brew update
|
||||
# - brew install caskroom/cask/brew-cask
|
||||
# - brew cask install osxfuse
|
||||
# - go run build/ci.go install
|
||||
# - go run build/ci.go test -coverage
|
||||
|
||||
# This builder only tests code linters on latest version of Go
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.10.x
|
||||
env:
|
||||
- lint
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- go run build/ci.go lint
|
||||
-
|
||||
go get github.com/alecthomas/gometalinter && gometalinter --install && gometalinter
|
||||
- os: linux
|
||||
env: V=0.15.0
|
||||
before_install:
|
||||
- |
|
||||
if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then
|
||||
OS=darwin
|
||||
else
|
||||
sysctl kernel.unprivileged_userns_clone=1
|
||||
OS=linux
|
||||
fi
|
||||
if [[ "${V}" == "HEAD" ]]; then
|
||||
# Determine last successful build number. This may change while we are
|
||||
# downloading, so it's important to determine ahead of time, in case
|
||||
# we need to resume the download.
|
||||
CI_BASE="http://ci.bazel.io/view/Bazel%20bootstrap%20and%20maintenance/job/Bazel/PLATFORM_NAME=${OS}-x86_64/"
|
||||
CI_INDEX_URL="${CI_BASE}/lastSuccessfulBuild/"
|
||||
wget -q -O build-index.html "${CI_INDEX_URL}"
|
||||
CI_BUILD=$(grep '<title>' build-index.html | sed -e 's/^.*#\([0-9]*\).*$/\1/')
|
||||
# Determine the artifact name. This is normally, bazel--installer.sh,
|
||||
# but it may be, for example, bazel-0.5rc2-installer.sh before a release.
|
||||
CI_ARTIFACT=$(grep -o 'bazel-[^\"-]*-installer.sh' build-index.html | head -n 1)
|
||||
URL="${CI_BASE}/${CI_BUILD}/artifact/output/ci/${CI_ARTIFACT}"
|
||||
rm build-index.html
|
||||
else
|
||||
URL="https://github.com/bazelbuild/bazel/releases/download/${V}/bazel-${V}-installer-${OS}-x86_64.sh"
|
||||
fi
|
||||
wget -O install.sh "${URL}"
|
||||
chmod +x install.sh
|
||||
./install.sh --user
|
||||
rm -f install.sh
|
||||
|
||||
install: true # Skip install go packages.
|
||||
|
||||
script:
|
||||
# Run all tests.
|
||||
- |
|
||||
bazel \
|
||||
--bazelrc=.travis-bazelrc \
|
||||
test \
|
||||
//...
|
||||
|
||||
# Check that BUILD files are formatted correctly.
|
||||
- ./check_gazelle.sh
|
||||
|
||||
# Tasks below this line are for uploading distributables, this is not necessary at the moment
|
||||
# for Prysmatic Labs Geth Sharding implementation development.
|
||||
|
||||
# TODO: Enable these distributables for sharding once stable milestones have been reached.
|
||||
|
||||
# This builder does the Ubuntu PPA and Linux Azure uploads
|
||||
# - os: linux
|
||||
# dist: trusty
|
||||
# sudo: required
|
||||
# go: 1.9.x
|
||||
# env:
|
||||
# - ubuntu-ppa
|
||||
# - azure-linux
|
||||
# git:
|
||||
# submodules: false # avoid cloning ethereum/tests
|
||||
# addons:
|
||||
# apt:
|
||||
# packages:
|
||||
# - devscripts
|
||||
# - debhelper
|
||||
# - dput
|
||||
# - gcc-multilib
|
||||
# - fakeroot
|
||||
# script:
|
||||
# # Build for the primary platforms that Trusty can manage
|
||||
# - go run build/ci.go debsrc -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>" -upload ppa:ethereum/ethereum
|
||||
# - 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
|
||||
|
||||
# # 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
|
||||
|
||||
# This builder does the Linux Azure MIPS xgo uploads
|
||||
# - os: linux
|
||||
# dist: trusty
|
||||
# services:
|
||||
# - docker
|
||||
# go: 1.9.x
|
||||
# env:
|
||||
# - azure-linux-mips
|
||||
# git:
|
||||
# submodules: false # avoid cloning ethereum/tests
|
||||
# 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 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 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 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
|
||||
|
||||
# This builder does the Android Maven and Azure uploads
|
||||
# - os: linux
|
||||
# dist: precise # Needed for the android tools
|
||||
# addons:
|
||||
# apt:
|
||||
# packages:
|
||||
# - oracle-java8-installer
|
||||
# - oracle-java8-set-default
|
||||
# language: android
|
||||
# android:
|
||||
# components:
|
||||
# - platform-tools
|
||||
# - tools
|
||||
# - android-15
|
||||
# - android-19
|
||||
# - android-24
|
||||
# env:
|
||||
# - azure-android
|
||||
# - maven-android
|
||||
# git:
|
||||
# submodules: false # avoid cloning ethereum/tests
|
||||
# before_install:
|
||||
# - curl https://storage.googleapis.com/golang/go1.9.2.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-r15c-linux-x86_64.zip -o android-ndk-r15c.zip
|
||||
# - unzip -q android-ndk-r15c.zip && rm android-ndk-r15c.zip
|
||||
# - mv android-ndk-r15c $HOME
|
||||
# - export ANDROID_NDK=$HOME/android-ndk-r15c
|
||||
|
||||
# - mkdir -p $GOPATH/src/github.com/ethereum
|
||||
# - ln -s `pwd` $GOPATH/src/github.com/ethereum
|
||||
# - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
|
||||
|
||||
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
||||
# - os: osx
|
||||
# go: 1.9.x
|
||||
# env:
|
||||
# - azure-osx
|
||||
# - azure-ios
|
||||
# - cocoapods-ios
|
||||
# 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
|
||||
|
||||
# # Build the iOS framework and upload it to CocoaPods and Azure
|
||||
# - gem uninstall cocoapods -a -x
|
||||
# - gem install cocoapods
|
||||
|
||||
# - mv ~/.cocoapods/repos/master ~/.cocoapods/repos/master.bak
|
||||
# - sed -i '.bak' 's/repo.join/!repo.join/g' $(dirname `gem which cocoapods`)/cocoapods/sources_manager.rb
|
||||
# - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git clone --depth=1 https://github.com/CocoaPods/Specs.git ~/.cocoapods/repos/master && pod setup --verbose; fi
|
||||
|
||||
# - xctool -version
|
||||
# - xcrun simctl list
|
||||
|
||||
# - go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
||||
|
||||
# # This builder does the Azure archive purges to avoid accumulating junk
|
||||
# - os: linux
|
||||
# dist: trusty
|
||||
# sudo: required
|
||||
# go: 1.9.x
|
||||
# env:
|
||||
# - azure-purge
|
||||
# git:
|
||||
# submodules: false # avoid cloning ethereum/tests
|
||||
# script:
|
||||
# - go run build/ci.go purge -store gethstore/builds -days 14
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/e09ccdce1048c5e03445
|
||||
on_success: change
|
||||
on_failure: always
|
||||
# Shutdown must be last.
|
||||
- bazel shutdown
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
173
AUTHORS
173
AUTHORS
@@ -1,173 +0,0 @@
|
||||
# This is the official list of go-ethereum authors for copyright purposes.
|
||||
|
||||
Afri Schoedon <5chdn@users.noreply.github.com>
|
||||
Agustin Armellini Fischer <armellini13@gmail.com>
|
||||
Airead <fgh1987168@gmail.com>
|
||||
Alan Chen <alanchchen@users.noreply.github.com>
|
||||
Alejandro Isaza <alejandro.isaza@gmail.com>
|
||||
Ales Katona <ales@coinbase.com>
|
||||
Alex Leverington <alex@ethdev.com>
|
||||
Alex Wu <wuyiding@gmail.com>
|
||||
Alexandre Van de Sande <alex.vandesande@ethdev.com>
|
||||
Ali Hajimirza <Ali92hm@users.noreply.github.com>
|
||||
Anton Evangelatov <anton.evangelatov@gmail.com>
|
||||
Arba Sasmoyo <arba.sasmoyo@gmail.com>
|
||||
Armani Ferrante <armaniferrante@berkeley.edu>
|
||||
Armin Braun <me@obrown.io>
|
||||
Aron Fischer <github@aron.guru>
|
||||
Bas van Kervel <bas@ethdev.com>
|
||||
Benjamin Brent <benjamin@benjaminbrent.com>
|
||||
Benoit Verkindt <benoit.verkindt@gmail.com>
|
||||
Bo <bohende@gmail.com>
|
||||
Bo Ye <boy.e.computer.1982@outlook.com>
|
||||
Bob Glickstein <bobg@users.noreply.github.com>
|
||||
Brian Schroeder <bts@gmail.com>
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
Chase Wright <mysticryuujin@gmail.com>
|
||||
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||
Daniel A. Nagy <nagy.da@gmail.com>
|
||||
Daniel Sloof <goapsychadelic@gmail.com>
|
||||
Darrel Herbst <dherbst@gmail.com>
|
||||
Dave Appleton <calistralabs@gmail.com>
|
||||
Diego Siqueira <DiSiqueira@users.noreply.github.com>
|
||||
Dmitry Shulyak <yashulyak@gmail.com>
|
||||
Egon Elbre <egonelbre@gmail.com>
|
||||
Elias Naur <elias.naur@gmail.com>
|
||||
Elliot Shepherd <elliot@identitii.com>
|
||||
Enrique Fynn <enriquefynn@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ethan Buchman <ethan@coinculture.info>
|
||||
Eugene Valeyev <evgen.povt@gmail.com>
|
||||
Evangelos Pappas <epappas@evalonlabs.com>
|
||||
Evgeny Danilenko <6655321@bk.ru>
|
||||
Fabian Vogelsteller <fabian@frozeman.de>
|
||||
Fabio Barone <fabio.barone.co@gmail.com>
|
||||
Fabio Berger <fabioberger1991@gmail.com>
|
||||
FaceHo <facehoshi@gmail.com>
|
||||
Felix Lange <fjl@twurst.com>
|
||||
Fiisio <liangcszzu@163.com>
|
||||
Frank Wang <eternnoir@gmail.com>
|
||||
Furkan KAMACI <furkankamaci@gmail.com>
|
||||
Gary Rong <garyrong0905@gmail.com>
|
||||
George Ornbo <george@shapeshed.com>
|
||||
Gregg Dourgarian <greggd@tempworks.com>
|
||||
Guillaume Ballet <gballet@gmail.com>
|
||||
Guillaume Nicolas <guin56@gmail.com>
|
||||
Gustav Simonsson <gustav.simonsson@gmail.com>
|
||||
Hao Bryan Cheng <haobcheng@gmail.com>
|
||||
Henning Diedrich <hd@eonblast.com>
|
||||
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
|
||||
Ivan Daniluk <ivan.daniluk@gmail.com>
|
||||
Jae Kwon <jkwon.work@gmail.com>
|
||||
Jamie Pitts <james.pitts@gmail.com>
|
||||
Janoš Guljaš <janos@users.noreply.github.com>
|
||||
Jason Carver <jacarver@linkedin.com>
|
||||
Jay Guo <guojiannan1101@gmail.com>
|
||||
Jeff R. Allen <jra@nella.org>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||
Jens Agerberg <github@agerberg.me>
|
||||
Jia Chenhui <jiachenhui1989@gmail.com>
|
||||
Jim McDonald <Jim@mcdee.net>
|
||||
Joel Burget <joelburget@gmail.com>
|
||||
Jonathan Brown <jbrown@bluedroplet.com>
|
||||
Joseph Chow <ethereum@outlook.com>
|
||||
Justin Clark-Casey <justincc@justincc.org>
|
||||
Justin Drake <drakefjustin@gmail.com>
|
||||
Kenji Siu <kenji@isuntv.com>
|
||||
Kobi Gurkan <kobigurk@gmail.com>
|
||||
Konrad Feldmeier <konrad@brainbot.com>
|
||||
Kurkó Mihály <kurkomisi@users.noreply.github.com>
|
||||
Kyuntae Ethan Kim <ethan.kyuntae.kim@gmail.com>
|
||||
Lefteris Karapetsas <lefteris@refu.co>
|
||||
Leif Jurvetson <leijurv@gmail.com>
|
||||
Leo Shklovskii <leo@thermopylae.net>
|
||||
Lewis Marshall <lewis@lmars.net>
|
||||
Lio李欧 <lionello@users.noreply.github.com>
|
||||
Louis Holbrook <dev@holbrook.no>
|
||||
Luca Zeug <luclu@users.noreply.github.com>
|
||||
Magicking <s@6120.eu>
|
||||
Maran Hidskes <maran.hidskes@gmail.com>
|
||||
Marek Kotewicz <marek.kotewicz@gmail.com>
|
||||
Mark <markya0616@gmail.com>
|
||||
Martin Holst Swende <martin@swende.se>
|
||||
Matthew Di Ferrante <mattdf@users.noreply.github.com>
|
||||
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
|
||||
Maximilian Meister <mmeister@suse.de>
|
||||
Micah Zoltu <micah@zoltu.net>
|
||||
Michael Ruminer <michael.ruminer+github@gmail.com>
|
||||
Miguel Mota <miguelmota2@gmail.com>
|
||||
Miya Chen <miyatlchen@gmail.com>
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
Nick Dodson <silentcicero@outlook.com>
|
||||
Nick Johnson <arachnid@notdot.net>
|
||||
Nicolas Guillaume <gunicolas@sqli.com>
|
||||
Noman <noman@noman.land>
|
||||
Oli Bye <olibye@users.noreply.github.com>
|
||||
Paul Litvak <litvakpol@012.net.il>
|
||||
Paulo L F Casaretto <pcasaretto@gmail.com>
|
||||
Paweł Bylica <chfast@gmail.com>
|
||||
Peter Pratscher <pratscher@gmail.com>
|
||||
Petr Mikusek <petr@mikusek.info>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
Ramesh Nair <ram@hiddentao.com>
|
||||
Ricardo Catalinas Jiménez <r@untroubled.be>
|
||||
Ricardo Domingos <ricardohsd@gmail.com>
|
||||
Richard Hart <richardhart92@gmail.com>
|
||||
Rob <robert@rojotek.com>
|
||||
Robert Zaremba <robert.zaremba@scale-it.pl>
|
||||
Russ Cox <rsc@golang.org>
|
||||
Rémy Roy <remyroy@remyroy.com>
|
||||
S. Matthew English <s-matthew-english@users.noreply.github.com>
|
||||
Shintaro Kaneko <kaneshin0120@gmail.com>
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||
Stein Dekker <dekker.stein@gmail.com>
|
||||
Steve Waldman <swaldman@mchange.com>
|
||||
Steven Roose <stevenroose@gmail.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com>
|
||||
Thomas Bocek <tom@tomp2p.net>
|
||||
Ti Zhou <tizhou1986@gmail.com>
|
||||
Tosh Camille <tochecamille@gmail.com>
|
||||
Valentin Wüstholz <wuestholz@gmail.com>
|
||||
Victor Farazdagi <simple.square@gmail.com>
|
||||
Victor Tran <vu.tran54@gmail.com>
|
||||
Viktor Trón <viktor.tron@gmail.com>
|
||||
Ville Sundell <github@solarius.fi>
|
||||
Vincent G <caktux@gmail.com>
|
||||
Vitalik Buterin <v@buterin.com>
|
||||
Vitaly V <vvelikodny@gmail.com>
|
||||
Vivek Anand <vivekanand1101@users.noreply.github.com>
|
||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||
Yohann Léon <sybiload@gmail.com>
|
||||
Yoichi Hirai <i@yoichihirai.com>
|
||||
Yondon Fu <yondon.fu@gmail.com>
|
||||
Zach <zach.ramsay@gmail.com>
|
||||
Zahoor Mohamed <zahoor@zahoor.in>
|
||||
Zoe Nolan <github@zoenolan.org>
|
||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||
am2rican5 <am2rican5@gmail.com>
|
||||
ayeowch <ayeowch@gmail.com>
|
||||
b00ris <b00ris@mail.ru>
|
||||
bailantaotao <Edwin@maicoin.com>
|
||||
baizhenxuan <nkbai@163.com>
|
||||
bloonfield <bloonfield@163.com>
|
||||
changhong <changhong.yu@shanbay.com>
|
||||
evgk <evgeniy.kamyshev@gmail.com>
|
||||
ferhat elmas <elmas.ferhat@gmail.com>
|
||||
holisticode <holistic.computing@gmail.com>
|
||||
jtakalai <juuso.takalainen@streamr.com>
|
||||
ken10100147 <sunhongping@kanjian.com>
|
||||
ligi <ligi@ligi.de>
|
||||
mark.lin <mark@maicoin.com>
|
||||
necaremus <necaremus@gmail.com>
|
||||
njupt-moon <1015041018@njupt.edu.cn>
|
||||
nkbai <nkbai@163.com>
|
||||
rhaps107 <dod-source@yandex.ru>
|
||||
slumber1122 <slumber1122@gmail.com>
|
||||
sunxiaojun2014 <sunxiaojun-xy@360.cn>
|
||||
terasum <terasum@163.com>
|
||||
tsarpaul <Litvakpol@012.net.il>
|
||||
xiekeyang <xiekeyang@users.noreply.github.com>
|
||||
yoza <yoza.is12s@gmail.com>
|
||||
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||
7
BUILD.bazel
Normal file
7
BUILD.bazel
Normal file
@@ -0,0 +1,7 @@
|
||||
load("@bazel_gazelle//:def.bzl", "gazelle")
|
||||
|
||||
# gazelle:prefix github.com/prysmaticlabs/geth-sharding
|
||||
gazelle(
|
||||
name = "gazelle",
|
||||
prefix = "github.com/prysmaticlabs/geth-sharding",
|
||||
)
|
||||
619
COPYING
619
COPYING
@@ -1,619 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2014 The go-ethereum Authors.
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
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.
|
||||
165
COPYING.LESSER
165
COPYING.LESSER
@@ -1,165 +0,0 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
16
Dockerfile
16
Dockerfile
@@ -1,16 +0,0 @@
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.10-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||
|
||||
ADD . /go-ethereum
|
||||
RUN cd /go-ethereum && make geth
|
||||
|
||||
# Pull Geth into a second stage deploy alpine container
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
||||
|
||||
EXPOSE 8545 8546 30303 30303/udp
|
||||
ENTRYPOINT ["geth"]
|
||||
@@ -1,15 +0,0 @@
|
||||
# Build Geth in a stock Go builder container
|
||||
FROM golang:1.10-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||
|
||||
ADD . /go-ethereum
|
||||
RUN cd /go-ethereum && make all
|
||||
|
||||
# Pull all binaries into a second stage deploy alpine container
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
||||
|
||||
EXPOSE 8545 8546 30303 30303/udp
|
||||
149
Makefile
149
Makefile
@@ -1,149 +0,0 @@
|
||||
# This Makefile is meant to be used by people that do not usually work
|
||||
# with Go source code. If you know what GOPATH is then you probably
|
||||
# don't need to bother with make.
|
||||
|
||||
.PHONY: geth android ios geth-cross swarm evm all test clean
|
||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
|
||||
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
|
||||
.PHONY: geth-windows geth-windows-386 geth-windows-amd64
|
||||
|
||||
GOBIN = $(shell pwd)/build/bin
|
||||
GO ?= latest
|
||||
|
||||
geth:
|
||||
build/env.sh go run build/ci.go install ./cmd/geth
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||
|
||||
swarm:
|
||||
build/env.sh go run build/ci.go install ./cmd/swarm
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/swarm\" to launch swarm."
|
||||
|
||||
all:
|
||||
build/env.sh go run build/ci.go install
|
||||
|
||||
android:
|
||||
build/env.sh go run build/ci.go aar --local
|
||||
@echo "Done building."
|
||||
@echo "Import \"$(GOBIN)/geth.aar\" to use the library."
|
||||
|
||||
ios:
|
||||
build/env.sh go run build/ci.go xcode --local
|
||||
@echo "Done building."
|
||||
@echo "Import \"$(GOBIN)/Geth.framework\" to use the library."
|
||||
|
||||
test: all
|
||||
build/env.sh go run build/ci.go test
|
||||
|
||||
lint: ## Run linters.
|
||||
build/env.sh go run build/ci.go lint
|
||||
|
||||
clean:
|
||||
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
||||
|
||||
# The devtools target installs tools required for 'go generate'.
|
||||
# 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 ./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'
|
||||
|
||||
# Cross Compilation Targets (xgo)
|
||||
|
||||
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||
@echo "Full cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-*
|
||||
|
||||
geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm geth-linux-mips64 geth-linux-mips64le
|
||||
@echo "Linux cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-*
|
||||
|
||||
geth-linux-386:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/386 -v ./cmd/geth
|
||||
@echo "Linux 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep 386
|
||||
|
||||
geth-linux-amd64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/amd64 -v ./cmd/geth
|
||||
@echo "Linux amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep amd64
|
||||
|
||||
geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||
@echo "Linux ARM cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm
|
||||
|
||||
geth-linux-arm-5:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-5 -v ./cmd/geth
|
||||
@echo "Linux ARMv5 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-5
|
||||
|
||||
geth-linux-arm-6:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-6 -v ./cmd/geth
|
||||
@echo "Linux ARMv6 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-6
|
||||
|
||||
geth-linux-arm-7:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-7 -v ./cmd/geth
|
||||
@echo "Linux ARMv7 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-7
|
||||
|
||||
geth-linux-arm64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm64 -v ./cmd/geth
|
||||
@echo "Linux ARM64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm64
|
||||
|
||||
geth-linux-mips:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||
@echo "Linux MIPS cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips
|
||||
|
||||
geth-linux-mipsle:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mipsle --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||
@echo "Linux MIPSle cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mipsle
|
||||
|
||||
geth-linux-mips64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64 --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||
@echo "Linux MIPS64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips64
|
||||
|
||||
geth-linux-mips64le:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64le --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||
@echo "Linux MIPS64le cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips64le
|
||||
|
||||
geth-darwin: geth-darwin-386 geth-darwin-amd64
|
||||
@echo "Darwin cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-*
|
||||
|
||||
geth-darwin-386:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/386 -v ./cmd/geth
|
||||
@echo "Darwin 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-* | grep 386
|
||||
|
||||
geth-darwin-amd64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/amd64 -v ./cmd/geth
|
||||
@echo "Darwin amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-* | grep amd64
|
||||
|
||||
geth-windows: geth-windows-386 geth-windows-amd64
|
||||
@echo "Windows cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-*
|
||||
|
||||
geth-windows-386:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/386 -v ./cmd/geth
|
||||
@echo "Windows 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-* | grep 386
|
||||
|
||||
geth-windows-amd64:
|
||||
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/amd64 -v ./cmd/geth
|
||||
@echo "Windows amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-* | grep amd64
|
||||
@@ -136,7 +136,7 @@ Omitting the `--actor` flag will launch a simple observer service attached to th
|
||||
|
||||
The Sharding Manager Contract is built in Solidity and deployed to a running geth node upon launch of the sharding node if it does not exist in the network at a specified address. If there are any changes to the SMC's code, the Golang bindigs must be rebuilt with the following command.
|
||||
|
||||
go generate github.com/ethereum/go-ethereum/sharding
|
||||
go generate github.com/prysmaticlabs/geth-sharding/sharding
|
||||
# OR
|
||||
cd sharding && go generate
|
||||
|
||||
@@ -145,7 +145,7 @@ The Sharding Manager Contract is built in Solidity and deployed to a running get
|
||||
To run the unit tests of our system do:
|
||||
|
||||
```
|
||||
go test github.com/ethereum/go-ethereum/sharding
|
||||
go test github.com/prysmaticlabs/geth-sharding/sharding
|
||||
```
|
||||
|
||||
We will require more complex testing scenarios (fuzz tests) to measure the full integrity of the system as it evolves.
|
||||
|
||||
50
WORKSPACE
Normal file
50
WORKSPACE
Normal file
@@ -0,0 +1,50 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_go",
|
||||
urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.12.1/rules_go-0.12.1.tar.gz"],
|
||||
sha256 = "8b68d0630d63d95dacc0016c3bb4b76154fe34fca93efd65d1c366de3fcb4294",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "bazel_gazelle",
|
||||
urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.12.0/bazel-gazelle-0.12.0.tar.gz"],
|
||||
sha256 = "ddedc7aaeb61f2654d7d7d4fd7940052ea992ccdb031b8f9797ed143ac7e8d43",
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
|
||||
go_rules_dependencies()
|
||||
|
||||
go_register_toolchains()
|
||||
|
||||
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
|
||||
|
||||
gazelle_dependencies()
|
||||
|
||||
go_repository(
|
||||
name = "com_github_ethereum_go_ethereum",
|
||||
importpath = "github.com/ethereum/go-ethereum",
|
||||
# Note: go-ethereum is not bazel-friendly with regards to cgo. We have a
|
||||
# a fork that has resolved these issues by disabling HID/USB support and
|
||||
# some manual fixes for c imports in the crypto package. This is forked
|
||||
# branch should be updated from time to time with the latest go-ethereum
|
||||
# code.
|
||||
remote = "https://github.com/prysmaticlabs/bazel-go-ethereum",
|
||||
vcs = "git",
|
||||
# Last updated July 5, 2018
|
||||
commit = "eb95493d32b6e1eb1cad63518637e1a958632389",
|
||||
)
|
||||
|
||||
# TODO: Update this to use github.com/urfave/cli.
|
||||
go_repository(
|
||||
name = "in_gopkg_urfave_cli_v1",
|
||||
importpath = "gopkg.in/urfave/cli.v1",
|
||||
commit = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "com_github_fjl_memsize",
|
||||
importpath = "github.com/fjl/memsize",
|
||||
commit = "ca190fb6ffbc076ff49197b7168a760f30182d2e",
|
||||
)
|
||||
@@ -1,146 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The ABI holds information about a contract's context and available
|
||||
// invokable methods. It will allow you to type check function calls and
|
||||
// packs data accordingly.
|
||||
type ABI struct {
|
||||
Constructor Method
|
||||
Methods map[string]Method
|
||||
Events map[string]Event
|
||||
}
|
||||
|
||||
// JSON returns a parsed ABI interface and error if it failed.
|
||||
func JSON(reader io.Reader) (ABI, error) {
|
||||
dec := json.NewDecoder(reader)
|
||||
|
||||
var abi ABI
|
||||
if err := dec.Decode(&abi); err != nil {
|
||||
return ABI{}, err
|
||||
}
|
||||
|
||||
return abi, nil
|
||||
}
|
||||
|
||||
// Pack the given method name to conform the ABI. Method call's data
|
||||
// will consist of method_id, args0, arg1, ... argN. Method id consists
|
||||
// of 4 bytes and arguments are all 32 bytes.
|
||||
// Method ids are created from the first 4 bytes of the hash of the
|
||||
// methods string signature. (signature = baz(uint32,string32))
|
||||
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||
// Fetch the ABI of the requested method
|
||||
if name == "" {
|
||||
// constructor
|
||||
arguments, err := abi.Constructor.Inputs.Pack(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return arguments, nil
|
||||
|
||||
}
|
||||
method, exist := abi.Methods[name]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("method '%s' not found", name)
|
||||
}
|
||||
|
||||
arguments, err := method.Inputs.Pack(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pack up the method ID too if not a constructor and return
|
||||
return append(method.Id(), arguments...), nil
|
||||
}
|
||||
|
||||
// Unpack output in v according to the abi specification
|
||||
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||
if len(output) == 0 {
|
||||
return fmt.Errorf("abi: unmarshalling empty output")
|
||||
}
|
||||
// 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(output)%32 != 0 {
|
||||
return fmt.Errorf("abi: improperly formatted output")
|
||||
}
|
||||
return method.Outputs.Unpack(v, output)
|
||||
} else if event, ok := abi.Events[name]; ok {
|
||||
return event.Inputs.Unpack(v, output)
|
||||
}
|
||||
return fmt.Errorf("abi: could not locate named method or event")
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface
|
||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||
var fields []struct {
|
||||
Type string
|
||||
Name string
|
||||
Constant bool
|
||||
Anonymous bool
|
||||
Inputs []Argument
|
||||
Outputs []Argument
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &fields); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
abi.Methods = make(map[string]Method)
|
||||
abi.Events = make(map[string]Event)
|
||||
for _, field := range fields {
|
||||
switch field.Type {
|
||||
case "constructor":
|
||||
abi.Constructor = Method{
|
||||
Inputs: field.Inputs,
|
||||
}
|
||||
// empty defaults to function according to the abi spec
|
||||
case "function", "":
|
||||
abi.Methods[field.Name] = Method{
|
||||
Name: field.Name,
|
||||
Const: field.Constant,
|
||||
Inputs: field.Inputs,
|
||||
Outputs: field.Outputs,
|
||||
}
|
||||
case "event":
|
||||
abi.Events[field.Name] = Event{
|
||||
Name: field.Name,
|
||||
Anonymous: field.Anonymous,
|
||||
Inputs: field.Inputs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MethodById looks up a method by the 4-byte id
|
||||
// returns nil if none found
|
||||
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
||||
for _, method := range abi.Methods {
|
||||
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||
return &method, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
|
||||
}
|
||||
@@ -1,715 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
const jsondata = `
|
||||
[
|
||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
||||
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
|
||||
]`
|
||||
|
||||
const jsondata2 = `
|
||||
[
|
||||
{ "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[]" } ] }
|
||||
]`
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
Uint256, _ := NewType("uint256")
|
||||
exp := ABI{
|
||||
Methods: map[string]Method{
|
||||
"balance": {
|
||||
"balance", true, nil, nil,
|
||||
},
|
||||
"send": {
|
||||
"send", false, []Argument{
|
||||
{"amount", Uint256, false},
|
||||
}, nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
abi, err := JSON(strings.NewReader(jsondata))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// deep equal fails for some reason
|
||||
for name, expM := range exp.Methods {
|
||||
gotM, exist := abi.Methods[name]
|
||||
if !exist {
|
||||
t.Errorf("Missing expected method %v", name)
|
||||
}
|
||||
if !reflect.DeepEqual(gotM, expM) {
|
||||
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
|
||||
}
|
||||
}
|
||||
|
||||
for name, gotM := range abi.Methods {
|
||||
expM, exist := exp.Methods[name]
|
||||
if !exist {
|
||||
t.Errorf("Found extra method %v", name)
|
||||
}
|
||||
if !reflect.DeepEqual(gotM, expM) {
|
||||
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestNumbers(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if _, err := abi.Pack("balance"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := abi.Pack("balance", 1); err == nil {
|
||||
t.Error("expected error for balance(1)")
|
||||
}
|
||||
|
||||
if _, err := abi.Pack("doesntexist", nil); err == nil {
|
||||
t.Errorf("doesntexist shouldn't exist")
|
||||
}
|
||||
|
||||
if _, err := abi.Pack("doesntexist", 1); err == nil {
|
||||
t.Errorf("doesntexist(1) shouldn't exist")
|
||||
}
|
||||
|
||||
if _, err := abi.Pack("send", big.NewInt(1000)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
i := new(int)
|
||||
*i = 1000
|
||||
if _, err := abi.Pack("send", i); err == nil {
|
||||
t.Errorf("expected send( ptr ) to throw, requires *big.Int instead of *int")
|
||||
}
|
||||
|
||||
if _, err := abi.Pack("test", uint32(1000)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestString(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
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.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
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.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
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")
|
||||
m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
|
||||
exp := "foo(string,string)"
|
||||
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)
|
||||
}
|
||||
|
||||
uintt, _ := NewType("uint256")
|
||||
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
|
||||
exp = "foo(uint256)"
|
||||
if m.Sig() != exp {
|
||||
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPack(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
sig := crypto.Keccak256([]byte("bar(uint32,uint16)"))[:4]
|
||||
sig = append(sig, make([]byte, 64)...)
|
||||
sig[35] = 10
|
||||
sig[67] = 11
|
||||
|
||||
packed, err := abi.Pack("bar", uint32(10), uint16(11))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleJSON() {
|
||||
const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
out, err := abi.Pack("isBar", common.HexToAddress("01"))
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%x\n", out)
|
||||
// Output:
|
||||
// 1f2c40920000000000000000000000000000000000000000000000000000000000000001
|
||||
}
|
||||
|
||||
func TestInputVariableInputLength(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
||||
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
||||
{ "type" : "function", "name" : "strTwo", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] }
|
||||
]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test one string
|
||||
strin := "hello world"
|
||||
strpack, err := abi.Pack("strOne", strin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
offset := make([]byte, 32)
|
||||
offset[31] = 32
|
||||
length := make([]byte, 32)
|
||||
length[31] = byte(len(strin))
|
||||
value := common.RightPadBytes([]byte(strin), 32)
|
||||
exp := append(offset, append(length, value...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
strpack = strpack[4:]
|
||||
if !bytes.Equal(strpack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, strpack)
|
||||
}
|
||||
|
||||
// test one bytes
|
||||
btspack, err := abi.Pack("bytesOne", []byte(strin))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
btspack = btspack[4:]
|
||||
if !bytes.Equal(btspack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, btspack)
|
||||
}
|
||||
|
||||
// test two strings
|
||||
str1 := "hello"
|
||||
str2 := "world"
|
||||
str2pack, err := abi.Pack("strTwo", str1, str2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
offset1 := make([]byte, 32)
|
||||
offset1[31] = 64
|
||||
length1 := make([]byte, 32)
|
||||
length1[31] = byte(len(str1))
|
||||
value1 := common.RightPadBytes([]byte(str1), 32)
|
||||
|
||||
offset2 := make([]byte, 32)
|
||||
offset2[31] = 128
|
||||
length2 := make([]byte, 32)
|
||||
length2[31] = byte(len(str2))
|
||||
value2 := common.RightPadBytes([]byte(str2), 32)
|
||||
|
||||
exp2 := append(offset1, offset2...)
|
||||
exp2 = append(exp2, append(length1, value1...)...)
|
||||
exp2 = append(exp2, append(length2, value2...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
str2pack = str2pack[4:]
|
||||
if !bytes.Equal(str2pack, exp2) {
|
||||
t.Errorf("expected %x, got %x\n", exp, str2pack)
|
||||
}
|
||||
|
||||
// test two strings, first > 32, second < 32
|
||||
str1 = strings.Repeat("a", 33)
|
||||
str2pack, err = abi.Pack("strTwo", str1, str2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
offset1 = make([]byte, 32)
|
||||
offset1[31] = 64
|
||||
length1 = make([]byte, 32)
|
||||
length1[31] = byte(len(str1))
|
||||
value1 = common.RightPadBytes([]byte(str1), 64)
|
||||
offset2[31] = 160
|
||||
|
||||
exp2 = append(offset1, offset2...)
|
||||
exp2 = append(exp2, append(length1, value1...)...)
|
||||
exp2 = append(exp2, append(length2, value2...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
str2pack = str2pack[4:]
|
||||
if !bytes.Equal(str2pack, exp2) {
|
||||
t.Errorf("expected %x, got %x\n", exp, str2pack)
|
||||
}
|
||||
|
||||
// test two strings, first > 32, second >32
|
||||
str1 = strings.Repeat("a", 33)
|
||||
str2 = strings.Repeat("a", 33)
|
||||
str2pack, err = abi.Pack("strTwo", str1, str2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
offset1 = make([]byte, 32)
|
||||
offset1[31] = 64
|
||||
length1 = make([]byte, 32)
|
||||
length1[31] = byte(len(str1))
|
||||
value1 = common.RightPadBytes([]byte(str1), 64)
|
||||
|
||||
offset2 = make([]byte, 32)
|
||||
offset2[31] = 160
|
||||
length2 = make([]byte, 32)
|
||||
length2[31] = byte(len(str2))
|
||||
value2 = common.RightPadBytes([]byte(str2), 64)
|
||||
|
||||
exp2 = append(offset1, offset2...)
|
||||
exp2 = append(exp2, append(length1, value1...)...)
|
||||
exp2 = append(exp2, append(length2, value2...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
str2pack = str2pack[4:]
|
||||
if !bytes.Equal(str2pack, exp2) {
|
||||
t.Errorf("expected %x, got %x\n", exp, str2pack)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2]
|
||||
strin := "hello world"
|
||||
arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
offset := make([]byte, 32)
|
||||
offset[31] = 96
|
||||
length := make([]byte, 32)
|
||||
length[31] = byte(len(strin))
|
||||
strvalue := common.RightPadBytes([]byte(strin), 32)
|
||||
arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32)
|
||||
arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32)
|
||||
exp := append(offset, arrinvalue1...)
|
||||
exp = append(exp, arrinvalue2...)
|
||||
exp = append(exp, append(length, strvalue...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
fixedArrStrPack = fixedArrStrPack[4:]
|
||||
if !bytes.Equal(fixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, fixedArrStrPack)
|
||||
}
|
||||
|
||||
// test byte array, fixed array uint256[2]
|
||||
bytesin := []byte(strin)
|
||||
arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
offset = make([]byte, 32)
|
||||
offset[31] = 96
|
||||
length = make([]byte, 32)
|
||||
length[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32)
|
||||
arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32)
|
||||
exp = append(offset, arrinvalue1...)
|
||||
exp = append(exp, arrinvalue2...)
|
||||
exp = append(exp, append(length, strvalue...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
fixedArrBytesPack = fixedArrBytesPack[4:]
|
||||
if !bytes.Equal(fixedArrBytesPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, fixedArrBytesPack)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2], dynamic array uint256[]
|
||||
strin = "hello world"
|
||||
fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||
mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
stroffset := make([]byte, 32)
|
||||
stroffset[31] = 128
|
||||
strlength := make([]byte, 32)
|
||||
strlength[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
fixedarrinvalue1 := common.LeftPadBytes(fixedarrin[0].Bytes(), 32)
|
||||
fixedarrinvalue2 := common.LeftPadBytes(fixedarrin[1].Bytes(), 32)
|
||||
dynarroffset := make([]byte, 32)
|
||||
dynarroffset[31] = byte(160 + ((len(strin)/32)+1)*32)
|
||||
dynarrlength := make([]byte, 32)
|
||||
dynarrlength[31] = byte(len(dynarrin))
|
||||
dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32)
|
||||
dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32)
|
||||
dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32)
|
||||
exp = append(stroffset, fixedarrinvalue1...)
|
||||
exp = append(exp, fixedarrinvalue2...)
|
||||
exp = append(exp, dynarroffset...)
|
||||
exp = append(exp, append(strlength, strvalue...)...)
|
||||
dynarrarg := append(dynarrlength, dynarrinvalue1...)
|
||||
dynarrarg = append(dynarrarg, dynarrinvalue2...)
|
||||
dynarrarg = append(dynarrarg, dynarrinvalue3...)
|
||||
exp = append(exp, dynarrarg...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
mixedArrStrPack = mixedArrStrPack[4:]
|
||||
if !bytes.Equal(mixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, mixedArrStrPack)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2], fixed array uint256[3]
|
||||
strin = "hello world"
|
||||
fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||
doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
stroffset = make([]byte, 32)
|
||||
stroffset[31] = 192
|
||||
strlength = make([]byte, 32)
|
||||
strlength[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
fixedarrin1value1 := common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
|
||||
fixedarrin1value2 := common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
|
||||
fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
|
||||
fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
|
||||
fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
|
||||
exp = append(stroffset, fixedarrin1value1...)
|
||||
exp = append(exp, fixedarrin1value2...)
|
||||
exp = append(exp, fixedarrin2value1...)
|
||||
exp = append(exp, fixedarrin2value2...)
|
||||
exp = append(exp, fixedarrin2value3...)
|
||||
exp = append(exp, append(strlength, strvalue...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
doubleFixedArrStrPack = doubleFixedArrStrPack[4:]
|
||||
if !bytes.Equal(doubleFixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, doubleFixedArrStrPack)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2], dynamic array uint256[], fixed array uint256[3]
|
||||
strin = "hello world"
|
||||
fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||
multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
stroffset = make([]byte, 32)
|
||||
stroffset[31] = 224
|
||||
strlength = make([]byte, 32)
|
||||
strlength[31] = byte(len(strin))
|
||||
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)))
|
||||
dynarrlength = make([]byte, 32)
|
||||
dynarrlength[31] = byte(len(dynarrin))
|
||||
dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32)
|
||||
dynarrinvalue2 = common.LeftPadBytes(dynarrin[1].Bytes(), 32)
|
||||
fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
|
||||
fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
|
||||
fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
|
||||
exp = append(stroffset, fixedarrin1value1...)
|
||||
exp = append(exp, fixedarrin1value2...)
|
||||
exp = append(exp, dynarroffset...)
|
||||
exp = append(exp, fixedarrin2value1...)
|
||||
exp = append(exp, fixedarrin2value2...)
|
||||
exp = append(exp, fixedarrin2value3...)
|
||||
exp = append(exp, append(strlength, strvalue...)...)
|
||||
dynarrarg = append(dynarrlength, dynarrinvalue1...)
|
||||
dynarrarg = append(dynarrarg, dynarrinvalue2...)
|
||||
exp = append(exp, dynarrarg...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
multipleMixedArrStrPack = multipleMixedArrStrPack[4:]
|
||||
if !bytes.Equal(multipleMixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, multipleMixedArrStrPack)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultFunctionParsing(t *testing.T) {
|
||||
const definition = `[{ "name" : "balance" }]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, ok := abi.Methods["balance"]; !ok {
|
||||
t.Error("expected 'balance' to be present")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBareEvents(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "type" : "event", "name" : "balance" },
|
||||
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
||||
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }
|
||||
]`
|
||||
|
||||
arg0, _ := NewType("uint256")
|
||||
arg1, _ := NewType("address")
|
||||
|
||||
expectedEvents := map[string]struct {
|
||||
Anonymous bool
|
||||
Args []Argument
|
||||
}{
|
||||
"balance": {false, nil},
|
||||
"anon": {true, nil},
|
||||
"args": {false, []Argument{
|
||||
{Name: "arg0", Type: arg0, Indexed: false},
|
||||
{Name: "arg1", Type: arg1, Indexed: true},
|
||||
}},
|
||||
}
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(abi.Events) != len(expectedEvents) {
|
||||
t.Fatalf("invalid number of events after parsing, want %d, got %d", len(expectedEvents), len(abi.Events))
|
||||
}
|
||||
|
||||
for name, exp := range expectedEvents {
|
||||
got, ok := abi.Events[name]
|
||||
if !ok {
|
||||
t.Errorf("could not found event %s", name)
|
||||
continue
|
||||
}
|
||||
if got.Anonymous != exp.Anonymous {
|
||||
t.Errorf("invalid anonymous indication for event %s, want %v, got %v", name, exp.Anonymous, got.Anonymous)
|
||||
}
|
||||
if len(got.Inputs) != len(exp.Args) {
|
||||
t.Errorf("invalid number of args, want %d, got %d", len(exp.Args), len(got.Inputs))
|
||||
continue
|
||||
}
|
||||
for i, arg := range exp.Args {
|
||||
if arg.Name != got.Inputs[i].Name {
|
||||
t.Errorf("events[%s].Input[%d] has an invalid name, want %s, got %s", name, i, arg.Name, got.Inputs[i].Name)
|
||||
}
|
||||
if arg.Indexed != got.Inputs[i].Indexed {
|
||||
t.Errorf("events[%s].Input[%d] has an invalid indexed indication, want %v, got %v", name, i, arg.Indexed, got.Inputs[i].Indexed)
|
||||
}
|
||||
if arg.Type.T != got.Inputs[i].Type.T {
|
||||
t.Errorf("events[%s].Input[%d] has an invalid type, want %x, got %x", name, i, arg.Type.T, got.Inputs[i].Type.T)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnpackEvent is based on this contract:
|
||||
// contract T {
|
||||
// event received(address sender, uint amount, bytes memo);
|
||||
// event receivedAddr(address sender);
|
||||
// function receive(bytes memo) external payable {
|
||||
// received(msg.sender, msg.value, memo);
|
||||
// receivedAddr(msg.sender);
|
||||
// }
|
||||
// }
|
||||
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||
func TestUnpackEvent(t *testing.T) {
|
||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||
abi, err := JSON(strings.NewReader(abiJSON))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||
data, err := hex.DecodeString(hexdata)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data)%32 == 0 {
|
||||
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||
}
|
||||
|
||||
type ReceivedEvent struct {
|
||||
Address common.Address
|
||||
Amount *big.Int
|
||||
Memo []byte
|
||||
}
|
||||
var ev ReceivedEvent
|
||||
|
||||
err = abi.Unpack(&ev, "received", data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("len(data): %d; received event: %+v", len(data), ev)
|
||||
}
|
||||
|
||||
type ReceivedAddrEvent struct {
|
||||
Address common.Address
|
||||
}
|
||||
var receivedAddrEv ReceivedAddrEvent
|
||||
err = abi.Unpack(&receivedAddrEv, "receivedAddr", data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("len(data): %d; received event: %+v", len(data), receivedAddrEv)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, m := range abi.Methods {
|
||||
a := fmt.Sprintf("%v", m)
|
||||
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 %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,290 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Argument holds the name of the argument and the corresponding type.
|
||||
// Types are used when packing and testing arguments.
|
||||
type Argument struct {
|
||||
Name string
|
||||
Type Type
|
||||
Indexed bool // indexed is only used by events
|
||||
}
|
||||
|
||||
type Arguments []Argument
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface
|
||||
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
||||
var extarg struct {
|
||||
Name string
|
||||
Type string
|
||||
Indexed bool
|
||||
}
|
||||
err := json.Unmarshal(data, &extarg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("argument json err: %v", err)
|
||||
}
|
||||
|
||||
argument.Type, err = NewType(extarg.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
argument.Name = extarg.Name
|
||||
argument.Indexed = extarg.Indexed
|
||||
|
||||
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
|
||||
func (arguments Arguments) NonIndexed() Arguments {
|
||||
var ret []Argument
|
||||
for _, arg := range arguments {
|
||||
if !arg.Indexed {
|
||||
ret = append(ret, arg)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// If the interface is a struct, get of abi->struct_field mapping
|
||||
|
||||
var abi2struct map[string]string
|
||||
if kind == reflect.Struct {
|
||||
var err error
|
||||
abi2struct, err = mapAbiToStructFields(arguments, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i, arg := range arguments.NonIndexed() {
|
||||
|
||||
reflectValue := reflect.ValueOf(marshalledValues[i])
|
||||
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
if structField, ok := abi2struct[arg.Name]; ok {
|
||||
if err := set(value.FieldByName(structField), reflectValue, arg); 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, reflectValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := set(v.Elem(), reflectValue, arg); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
||||
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error {
|
||||
if len(marshalledValues) != 1 {
|
||||
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
|
||||
}
|
||||
|
||||
elem := reflect.ValueOf(v).Elem()
|
||||
kind := elem.Kind()
|
||||
reflectValue := reflect.ValueOf(marshalledValues[0])
|
||||
|
||||
var abi2struct map[string]string
|
||||
if kind == reflect.Struct {
|
||||
var err error
|
||||
if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil {
|
||||
return err
|
||||
}
|
||||
arg := arguments.NonIndexed()[0]
|
||||
if structField, ok := abi2struct[arg.Name]; ok {
|
||||
return set(elem.FieldByName(structField), reflectValue, arg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return set(elem, reflectValue, arguments.NonIndexed()[0])
|
||||
|
||||
}
|
||||
|
||||
// Computes the full size of an array;
|
||||
// i.e. counting nested arrays, which count towards size for unpacking.
|
||||
func getArraySize(arr *Type) int {
|
||||
size := arr.Size
|
||||
// Arrays can be nested, with each element being the same size
|
||||
arr = arr.Elem
|
||||
for arr.T == ArrayTy {
|
||||
// Keep multiplying by elem.Size while the elem is an array.
|
||||
size *= arr.Size
|
||||
arr = arr.Elem
|
||||
}
|
||||
// Now we have the full array size, including its children.
|
||||
return size
|
||||
}
|
||||
|
||||
// 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())
|
||||
virtualArgs := 0
|
||||
for index, arg := range arguments.NonIndexed() {
|
||||
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
|
||||
if arg.Type.T == ArrayTy {
|
||||
// If we have a static array, like [3]uint256, these are coded as
|
||||
// just like uint256,uint256,uint256.
|
||||
// This means that we need to add two 'virtual' arguments when
|
||||
// we count the index from now on.
|
||||
//
|
||||
// Array values nested multiple levels deep are also encoded inline:
|
||||
// [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
|
||||
//
|
||||
// Calculate the full array size to get the correct offset for the next argument.
|
||||
// Decrement it by 1, as the normal index increment is still applied.
|
||||
virtualArgs += getArraySize(&arg.Type) - 1
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retval = append(retval, marshalledValue)
|
||||
}
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
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))
|
||||
}
|
||||
// variable input is the output appended at the end of packed
|
||||
// output. This is used for strings and bytes types input.
|
||||
var variableInput []byte
|
||||
|
||||
// input offset is the bytes offset for packed output
|
||||
inputOffset := 0
|
||||
for _, abiArg := range abiArgs {
|
||||
if abiArg.Type.T == ArrayTy {
|
||||
inputOffset += 32 * abiArg.Type.Size
|
||||
} else {
|
||||
inputOffset += 32
|
||||
}
|
||||
}
|
||||
var ret []byte
|
||||
for i, a := range args {
|
||||
input := abiArgs[i]
|
||||
// pack the input
|
||||
packed, err := input.Type.pack(reflect.ValueOf(a))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check for a slice type (string, bytes, slice)
|
||||
if input.Type.requiresLengthPrefix() {
|
||||
// calculate the offset
|
||||
offset := inputOffset + len(variableInput)
|
||||
// set the offset
|
||||
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||
// Append the packed output to the variable input. The variable input
|
||||
// will be appended at the end of the input.
|
||||
variableInput = append(variableInput, packed...)
|
||||
} else {
|
||||
// append the packed value to the input
|
||||
ret = append(ret, packed...)
|
||||
}
|
||||
}
|
||||
// append the variable input at the end of the packed input
|
||||
ret = append(ret, variableInput...)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// capitalise makes the first character of a string upper case, also removing any
|
||||
// prefixing underscores from the variable names.
|
||||
func capitalise(input string) string {
|
||||
for len(input) > 0 && input[0] == '_' {
|
||||
input = input[1:]
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(input[:1]) + input[1:]
|
||||
}
|
||||
@@ -1,62 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// NewTransactor is a utility method to easily create a transaction signer from
|
||||
// an encrypted json key stream and the associated passphrase.
|
||||
func NewTransactor(keyin io.Reader, passphrase string) (*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 NewKeyedTransactor(key.PrivateKey), nil
|
||||
}
|
||||
|
||||
// NewKeyedTransactor is a utility method to easily create a transaction signer
|
||||
// from a single private key.
|
||||
func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
|
||||
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
return &TransactOpts{
|
||||
From: keyAddr,
|
||||
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||
if address != keyAddr {
|
||||
return nil, errors.New("not authorized to sign this account")
|
||||
}
|
||||
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx.WithSignature(signer, signature)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,112 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoCode is returned by call and transact operations for which the requested
|
||||
// recipient contract to operate on does not exist in the state db or does not
|
||||
// have any code associated with it (i.e. suicided).
|
||||
ErrNoCode = errors.New("no contract code at given address")
|
||||
|
||||
// This error is raised when attempting to perform a pending state action
|
||||
// on a backend that doesn't implement PendingContractCaller.
|
||||
ErrNoPendingState = errors.New("backend does not support pending state")
|
||||
|
||||
// This error is returned by WaitDeployed if contract creation leaves an
|
||||
// empty contract behind.
|
||||
ErrNoCodeAfterDeploy = errors.New("no contract code after deployment")
|
||||
)
|
||||
|
||||
// ContractCaller defines the methods needed to allow operating with contract on a read
|
||||
// only basis.
|
||||
type ContractCaller interface {
|
||||
// CodeAt returns the code of the given account. This is needed to differentiate
|
||||
// between contract internal errors and the local chain being out of sync.
|
||||
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
|
||||
// ContractCall executes an Ethereum contract call with the specified data as the
|
||||
// input.
|
||||
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
// PendingContractCaller defines methods to perform contract calls on the pending state.
|
||||
// Call will try to discover this interface when access to the pending state is requested.
|
||||
// If the backend does not support the pending state, Call returns ErrNoPendingState.
|
||||
type PendingContractCaller interface {
|
||||
// PendingCodeAt returns the code of the given account in the pending state.
|
||||
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
|
||||
// PendingCallContract executes an Ethereum contract call against the pending state.
|
||||
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
|
||||
// used when the user does not provide some needed values, but rather leaves it up
|
||||
// to the transactor to decide.
|
||||
type ContractTransactor interface {
|
||||
// PendingCodeAt returns the code of the given account in the pending state.
|
||||
PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)
|
||||
// PendingNonceAt retrieves the current pending nonce associated with an account.
|
||||
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
|
||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
||||
// execution of a transaction.
|
||||
SuggestGasPrice(ctx context.Context) (*big.Int, error)
|
||||
// EstimateGas tries to estimate the gas needed to execute a specific
|
||||
// transaction based on the current pending state of the backend blockchain.
|
||||
// There is no guarantee that this is the true gas limit requirement as other
|
||||
// transactions may be added or removed by miners, but it should provide a basis
|
||||
// for setting a reasonable default.
|
||||
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
||||
}
|
||||
|
||||
// ContractFilterer defines the methods needed to access log events using one-off
|
||||
// queries or continuous event subscriptions.
|
||||
type ContractFilterer interface {
|
||||
// FilterLogs executes a log filter operation, blocking during execution and
|
||||
// returning all the results in one batch.
|
||||
//
|
||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
|
||||
|
||||
// SubscribeFilterLogs creates a background log filtering operation, returning
|
||||
// a subscription immediately, which can be used to stream the found events.
|
||||
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
||||
}
|
||||
|
||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||
type DeployBackend interface {
|
||||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
||||
type ContractBackend interface {
|
||||
ContractCaller
|
||||
ContractTransactor
|
||||
ContractFilterer
|
||||
}
|
||||
@@ -1,476 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"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"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// This nil assignment ensures 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")
|
||||
var 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.
|
||||
type SimulatedBackend struct {
|
||||
database ethdb.Database // In memory database to store our testing data
|
||||
blockchain *core.BlockChain // Ethereum blockchain to handle the consensus
|
||||
|
||||
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
|
||||
|
||||
events *filters.EventSystem // Event system for filtering log events live
|
||||
|
||||
config *params.ChainConfig
|
||||
}
|
||||
|
||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||
// for testing purposes.
|
||||
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
||||
database := ethdb.NewMemDatabase()
|
||||
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
|
||||
genesis.MustCommit(database)
|
||||
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
|
||||
|
||||
backend := &SimulatedBackend{
|
||||
database: database,
|
||||
blockchain: blockchain,
|
||||
config: genesis.Config,
|
||||
events: filters.NewEventSystem(new(event.TypeMux), &filterBackend{database, blockchain}, false),
|
||||
}
|
||||
backend.rollback()
|
||||
return backend
|
||||
}
|
||||
|
||||
// Commit imports all the pending transactions as a single block and starts a
|
||||
// fresh new state.
|
||||
func (b *SimulatedBackend) Commit() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil {
|
||||
panic(err) // This cannot happen unless the simulator is wrong, fail in that case
|
||||
}
|
||||
b.rollback()
|
||||
}
|
||||
|
||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
||||
func (b *SimulatedBackend) Rollback() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.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())
|
||||
}
|
||||
|
||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
||||
func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
return statedb.GetCode(contract), nil
|
||||
}
|
||||
|
||||
// BalanceAt returns the wei balance of a certain account in the blockchain.
|
||||
func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
return statedb.GetBalance(contract), nil
|
||||
}
|
||||
|
||||
// NonceAt returns the nonce of a certain account in the blockchain.
|
||||
func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return 0, errBlockNumberUnsupported
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
return statedb.GetNonce(contract), nil
|
||||
}
|
||||
|
||||
// StorageAt returns the value of key in the storage of an account in the blockchain.
|
||||
func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
val := statedb.GetState(contract, key)
|
||||
return val[:], nil
|
||||
}
|
||||
|
||||
// TransactionReceipt returns the receipt of a transaction.
|
||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash)
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
||||
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
return b.pendingState.GetCode(contract), nil
|
||||
}
|
||||
|
||||
// CallContract executes a contract call.
|
||||
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number()) != 0 {
|
||||
return nil, errBlockNumberUnsupported
|
||||
}
|
||||
state, err := b.blockchain.State()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rval, _, _, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), state)
|
||||
return rval, err
|
||||
}
|
||||
|
||||
// PendingCallContract executes a contract call on the pending state.
|
||||
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
|
||||
|
||||
rval, _, _, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
|
||||
return rval, err
|
||||
}
|
||||
|
||||
// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving
|
||||
// the nonce currently pending for the account.
|
||||
func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
return b.pendingState.GetOrNewStateObject(account).Nonce(), nil
|
||||
}
|
||||
|
||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||
// chain doens't have miners, we just return a gas price of 1 for any call.
|
||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||
return big.NewInt(1), nil
|
||||
}
|
||||
|
||||
// EstimateGas executes the requested code against the currently pending block/state and
|
||||
// returns the used amount of gas.
|
||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
// Determine the lowest and highest possible gas limits to binary search in between
|
||||
var (
|
||||
lo uint64 = params.TxGas - 1
|
||||
hi uint64
|
||||
cap uint64
|
||||
)
|
||||
if call.Gas >= params.TxGas {
|
||||
hi = call.Gas
|
||||
} else {
|
||||
hi = b.pendingBlock.GasLimit()
|
||||
}
|
||||
cap = hi
|
||||
|
||||
// Create a helper to check if a gas allowance results in an executable transaction
|
||||
executable := func(gas uint64) bool {
|
||||
call.Gas = gas
|
||||
|
||||
snapshot := b.pendingState.Snapshot()
|
||||
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
|
||||
b.pendingState.RevertToSnapshot(snapshot)
|
||||
|
||||
if err != nil || failed {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
// Execute the binary search and hone in on an executable gas limit
|
||||
for lo+1 < hi {
|
||||
mid := (hi + lo) / 2
|
||||
if !executable(mid) {
|
||||
lo = mid
|
||||
} else {
|
||||
hi = mid
|
||||
}
|
||||
}
|
||||
// Reject the transaction as invalid if it still fails at the highest allowance
|
||||
if hi == cap {
|
||||
if !executable(hi) {
|
||||
return 0, errGasEstimationFailed
|
||||
}
|
||||
}
|
||||
return hi, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Ensure message is initialized properly.
|
||||
if call.GasPrice == nil {
|
||||
call.GasPrice = big.NewInt(1)
|
||||
}
|
||||
if call.Gas == 0 {
|
||||
call.Gas = 50000000
|
||||
}
|
||||
if call.Value == nil {
|
||||
call.Value = new(big.Int)
|
||||
}
|
||||
// Set infinite balance to the fake caller account.
|
||||
from := statedb.GetOrNewStateObject(call.From)
|
||||
from.SetBalance(math.MaxBig256)
|
||||
// Execute the call.
|
||||
msg := callmsg{call}
|
||||
|
||||
evmContext := core.NewEVMContext(msg, 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)
|
||||
|
||||
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
|
||||
}
|
||||
|
||||
// SendTransaction updates the pending block to include the given transaction.
|
||||
// It panics if the transaction is invalid.
|
||||
func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
sender, err := types.Sender(types.HomesteadSigner{}, tx)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("invalid transaction: %v", err))
|
||||
}
|
||||
nonce := b.pendingState.GetNonce(sender)
|
||||
if tx.Nonce() != nonce {
|
||||
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) {
|
||||
for _, tx := range b.pendingBlock.Transactions() {
|
||||
block.AddTxWithChain(b.blockchain, tx)
|
||||
}
|
||||
block.AddTxWithChain(b.blockchain, tx)
|
||||
})
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterLogs executes a log filter operation, blocking during execution and
|
||||
// returning all the results in one batch.
|
||||
//
|
||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
||||
// Initialize unset filter boundaried to run from genesis to chain head
|
||||
from := int64(0)
|
||||
if query.FromBlock != nil {
|
||||
from = query.FromBlock.Int64()
|
||||
}
|
||||
to := int64(-1)
|
||||
if query.ToBlock != nil {
|
||||
to = query.ToBlock.Int64()
|
||||
}
|
||||
// Construct and execute the filter
|
||||
filter := filters.New(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
|
||||
|
||||
logs, err := filter.Logs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]types.Log, len(logs))
|
||||
for i, log := range logs {
|
||||
res[i] = *log
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
||||
// subscription immediately, which can be used to stream the found events.
|
||||
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
// Subscribe to contract events
|
||||
sink := make(chan []*types.Log)
|
||||
|
||||
sub, err := b.events.SubscribeLogs(query, sink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Since we're getting logs in batches, we need to flatten them into a plain stream
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case logs := <-sink:
|
||||
for _, log := range logs {
|
||||
select {
|
||||
case ch <- *log:
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
// AdjustTime adds a time shift to the simulated clock.
|
||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
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()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 }
|
||||
|
||||
// filterBackend implements filters.Backend to support filtering for logs without
|
||||
// taking bloom-bits acceleration structures into account.
|
||||
type filterBackend struct {
|
||||
db ethdb.Database
|
||||
bc *core.BlockChain
|
||||
}
|
||||
|
||||
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
||||
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
||||
|
||||
func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) {
|
||||
if block == rpc.LatestBlockNumber {
|
||||
return fb.bc.CurrentHeader(), nil
|
||||
}
|
||||
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
||||
if number == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return rawdb.ReadReceipts(fb.db, hash, *number), nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
|
||||
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
||||
if number == nil {
|
||||
return nil, nil
|
||||
}
|
||||
receipts := rawdb.ReadReceipts(fb.db, hash, *number)
|
||||
if receipts == nil {
|
||||
return nil, nil
|
||||
}
|
||||
logs := make([][]*types.Log, len(receipts))
|
||||
for i, receipt := range receipts {
|
||||
logs[i] = receipt.Logs
|
||||
}
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
<-quit
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||
return fb.bc.SubscribeChainEvent(ch)
|
||||
}
|
||||
func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
||||
return fb.bc.SubscribeRemovedLogsEvent(ch)
|
||||
}
|
||||
func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||
return fb.bc.SubscribeLogsEvent(ch)
|
||||
}
|
||||
|
||||
func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
|
||||
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
|
||||
panic("not supported")
|
||||
}
|
||||
@@ -1,350 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
// CallOpts is the collection of options to fine tune a contract call request.
|
||||
type CallOpts struct {
|
||||
Pending bool // Whether to operate on the pending state or the last known one
|
||||
From common.Address // Optional the sender address, otherwise the first account is used
|
||||
|
||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||
}
|
||||
|
||||
// TransactOpts is the collection of authorization data required to create a
|
||||
// valid Ethereum transaction.
|
||||
type TransactOpts struct {
|
||||
From common.Address // Ethereum account to send the transaction from
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
// FilterOpts is the collection of options to fine tune filtering for events
|
||||
// within a bound contract.
|
||||
type FilterOpts struct {
|
||||
Start uint64 // Start of the queried range
|
||||
End *uint64 // End of the range (nil = latest)
|
||||
|
||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||
}
|
||||
|
||||
// WatchOpts is the collection of options to fine tune subscribing for events
|
||||
// within a bound contract.
|
||||
type WatchOpts struct {
|
||||
Start *uint64 // Start of the queried range (nil = latest)
|
||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||
}
|
||||
|
||||
// BoundContract is the base wrapper object that reflects a contract on the
|
||||
// Ethereum network. It contains a collection of methods that are used by the
|
||||
// higher level contract bindings to operate.
|
||||
type BoundContract struct {
|
||||
address common.Address // Deployment address of the contract on the Ethereum blockchain
|
||||
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
||||
caller ContractCaller // Read interface to interact with the blockchain
|
||||
transactor ContractTransactor // Write interface to interact with the blockchain
|
||||
filterer ContractFilterer // Event filtering to interact with the blockchain
|
||||
}
|
||||
|
||||
// NewBoundContract creates a low level contract interface through which calls
|
||||
// and transactions may be made through.
|
||||
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract {
|
||||
return &BoundContract{
|
||||
address: address,
|
||||
abi: abi,
|
||||
caller: caller,
|
||||
transactor: transactor,
|
||||
filterer: filterer,
|
||||
}
|
||||
}
|
||||
|
||||
// DeployContract deploys a contract onto the Ethereum blockchain and binds the
|
||||
// deployment address with a Go wrapper.
|
||||
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
|
||||
// Otherwise try to deploy the contract
|
||||
c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
|
||||
|
||||
input, err := c.abi.Pack("", params...)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
tx, err := c.transact(opts, nil, append(bytecode, input...))
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
c.address = crypto.CreateAddress(opts.From, tx.Nonce())
|
||||
return c.address, tx, c, nil
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// 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 {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
opts = new(CallOpts)
|
||||
}
|
||||
// Pack the input, call and unpack the results
|
||||
input, err := c.abi.Pack(method, params...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input}
|
||||
ctx = ensureContext(opts.Context)
|
||||
code []byte
|
||||
output []byte
|
||||
)
|
||||
if opts.Pending {
|
||||
pb, ok := c.caller.(PendingContractCaller)
|
||||
if !ok {
|
||||
return ErrNoPendingState
|
||||
}
|
||||
output, err = pb.PendingCallContract(ctx, msg)
|
||||
if err == nil && len(output) == 0 {
|
||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||
if code, err = pb.PendingCodeAt(ctx, c.address); err != nil {
|
||||
return err
|
||||
} else if len(code) == 0 {
|
||||
return ErrNoCode
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output, err = c.caller.CallContract(ctx, msg, nil)
|
||||
if err == nil && 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, nil); err != nil {
|
||||
return err
|
||||
} else if len(code) == 0 {
|
||||
return ErrNoCode
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.abi.Unpack(result, method, output)
|
||||
}
|
||||
|
||||
// Transact invokes the (paid) contract method with params as input values.
|
||||
func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||
// Otherwise pack up the parameters and invoke the contract
|
||||
input, err := c.abi.Pack(method, params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.transact(opts, &c.address, input)
|
||||
}
|
||||
|
||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) {
|
||||
return c.transact(opts, &c.address, nil)
|
||||
}
|
||||
|
||||
// transact executes an actual transaction invocation, first deriving any missing
|
||||
// authorization fields, and then scheduling the transaction for execution.
|
||||
func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, input []byte) (*types.Transaction, error) {
|
||||
var err error
|
||||
|
||||
// Ensure a valid value field and resolve the account nonce
|
||||
value := opts.Value
|
||||
if value == nil {
|
||||
value = new(big.Int)
|
||||
}
|
||||
var nonce uint64
|
||||
if opts.Nonce == nil {
|
||||
nonce, err = c.transactor.PendingNonceAt(ensureContext(opts.Context), opts.From)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve account nonce: %v", err)
|
||||
}
|
||||
} else {
|
||||
nonce = opts.Nonce.Uint64()
|
||||
}
|
||||
// Figure out the gas allowance and gas price values
|
||||
gasPrice := opts.GasPrice
|
||||
if gasPrice == nil {
|
||||
gasPrice, err = c.transactor.SuggestGasPrice(ensureContext(opts.Context))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to suggest gas price: %v", err)
|
||||
}
|
||||
}
|
||||
gasLimit := opts.GasLimit
|
||||
if gasLimit == 0 {
|
||||
// Gas estimation cannot succeed without code for method invocations
|
||||
if contract != nil {
|
||||
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
|
||||
return nil, err
|
||||
} else if len(code) == 0 {
|
||||
return nil, ErrNoCode
|
||||
}
|
||||
}
|
||||
// If the contract surely has code (or code is not needed), estimate the transaction
|
||||
msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
|
||||
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
|
||||
}
|
||||
}
|
||||
// Create the transaction, sign it and schedule it for execution
|
||||
var rawTx *types.Transaction
|
||||
if contract == nil {
|
||||
rawTx = types.NewContractCreation(nonce, value, gasLimit, gasPrice, input)
|
||||
} else {
|
||||
rawTx = types.NewTransaction(nonce, c.address, value, gasLimit, gasPrice, input)
|
||||
}
|
||||
if opts.Signer == nil {
|
||||
return nil, errors.New("no signer to authorize the transaction with")
|
||||
}
|
||||
signedTx, err := opts.Signer(types.HomesteadSigner{}, opts.From, rawTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.transactor.SendTransaction(ensureContext(opts.Context), signedTx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
// FilterLogs filters contract logs for past blocks, returning the necessary
|
||||
// channels to construct a strongly typed bound iterator on top of them.
|
||||
func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
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...)
|
||||
|
||||
topics, err := makeTopics(query...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Start the background filtering
|
||||
logs := make(chan types.Log, 128)
|
||||
|
||||
config := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{c.address},
|
||||
Topics: topics,
|
||||
FromBlock: new(big.Int).SetUint64(opts.Start),
|
||||
}
|
||||
if opts.End != nil {
|
||||
config.ToBlock = new(big.Int).SetUint64(*opts.End)
|
||||
}
|
||||
/* TODO(karalabe): Replace the rest of the method below with this when supported
|
||||
sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
|
||||
*/
|
||||
buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sub, err := event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
for _, log := range buff {
|
||||
select {
|
||||
case logs <- log:
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}), nil
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return logs, sub, nil
|
||||
}
|
||||
|
||||
// WatchLogs filters subscribes to contract logs for future blocks, returning a
|
||||
// subscription object that can be used to tear down the watcher.
|
||||
func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
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...)
|
||||
|
||||
topics, err := makeTopics(query...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Start the background filtering
|
||||
logs := make(chan types.Log, 128)
|
||||
|
||||
config := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{c.address},
|
||||
Topics: topics,
|
||||
}
|
||||
if opts.Start != nil {
|
||||
config.FromBlock = new(big.Int).SetUint64(*opts.Start)
|
||||
}
|
||||
sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return logs, sub, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var indexed abi.Arguments
|
||||
for _, arg := range c.abi.Events[event].Inputs {
|
||||
if arg.Indexed {
|
||||
indexed = append(indexed, arg)
|
||||
}
|
||||
}
|
||||
return parseTopics(out, indexed, log.Topics[1:])
|
||||
}
|
||||
|
||||
// ensureContext is a helper method to ensure a context is not nil, even if the
|
||||
// user specified it as such.
|
||||
func ensureContext(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
return context.TODO()
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
@@ -1,455 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package bind generates Ethereum contract Go bindings.
|
||||
//
|
||||
// Detailed usage document and tutorial available on the go-ethereum Wiki page:
|
||||
// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
|
||||
package bind
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
// Lang is a target programming language selector to generate bindings for.
|
||||
type Lang int
|
||||
|
||||
const (
|
||||
LangGo Lang = iota
|
||||
LangJava
|
||||
LangObjC
|
||||
)
|
||||
|
||||
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||
// to be used as is in client code, but rather as an intermediate struct which
|
||||
// enforces compile time type safety and naming convention opposed to having to
|
||||
// manually maintain hard coded strings that break on runtime.
|
||||
func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) {
|
||||
// Process each individual contract requested binding
|
||||
contracts := make(map[string]*tmplContract)
|
||||
|
||||
for i := 0; i < len(types); i++ {
|
||||
// Parse the actual ABI to generate the binding for
|
||||
evmABI, err := abi.JSON(strings.NewReader(abis[i]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Strip any whitespace from the JSON ABI
|
||||
strippedABI := strings.Map(func(r rune) rune {
|
||||
if unicode.IsSpace(r) {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}, abis[i])
|
||||
|
||||
// Extract the call and transact methods; events; and sort them alphabetically
|
||||
var (
|
||||
calls = make(map[string]*tmplMethod)
|
||||
transacts = make(map[string]*tmplMethod)
|
||||
events = make(map[string]*tmplEvent)
|
||||
)
|
||||
for _, original := range evmABI.Methods {
|
||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||
normalized := original
|
||||
normalized.Name = methodNormalizer[lang](original.Name)
|
||||
|
||||
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||
copy(normalized.Inputs, original.Inputs)
|
||||
for j, input := range normalized.Inputs {
|
||||
if input.Name == "" {
|
||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||
}
|
||||
}
|
||||
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
||||
copy(normalized.Outputs, original.Outputs)
|
||||
for j, output := range normalized.Outputs {
|
||||
if output.Name != "" {
|
||||
normalized.Outputs[j].Name = capitalise(output.Name)
|
||||
}
|
||||
}
|
||||
// Append the methods to the call or transact lists
|
||||
if original.Const {
|
||||
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||
} else {
|
||||
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||
}
|
||||
}
|
||||
for _, original := range evmABI.Events {
|
||||
// Skip anonymous events as they don't support explicit filtering
|
||||
if original.Anonymous {
|
||||
continue
|
||||
}
|
||||
// Normalize the event for capital cases and non-anonymous outputs
|
||||
normalized := original
|
||||
normalized.Name = methodNormalizer[lang](original.Name)
|
||||
|
||||
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||
copy(normalized.Inputs, original.Inputs)
|
||||
for j, input := range normalized.Inputs {
|
||||
// Indexed fields are input, non-indexed ones are outputs
|
||||
if input.Indexed {
|
||||
if input.Name == "" {
|
||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Append the event to the accumulator list
|
||||
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
||||
}
|
||||
contracts[types[i]] = &tmplContract{
|
||||
Type: capitalise(types[i]),
|
||||
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
||||
InputBin: strings.TrimSpace(bytecodes[i]),
|
||||
Constructor: evmABI.Constructor,
|
||||
Calls: calls,
|
||||
Transacts: transacts,
|
||||
Events: events,
|
||||
}
|
||||
}
|
||||
// Generate the contract template data content and render it
|
||||
data := &tmplData{
|
||||
Package: pkg,
|
||||
Contracts: contracts,
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
funcs := map[string]interface{}{
|
||||
"bindtype": bindType[lang],
|
||||
"bindtopictype": bindTopicType[lang],
|
||||
"namedtype": namedType[lang],
|
||||
"capitalise": capitalise,
|
||||
"decapitalise": decapitalise,
|
||||
}
|
||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
|
||||
if err := tmpl.Execute(buffer, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// For Go bindings pass the code through goimports to clean it up and double check
|
||||
if lang == LangGo {
|
||||
code, err := imports.Process(".", buffer.Bytes(), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||
}
|
||||
return string(code), nil
|
||||
}
|
||||
// For all others just return as is for now
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// bindType is a set of type binders that convert Solidity types to some supported
|
||||
// programming language types.
|
||||
var bindType = map[Lang]func(kind abi.Type) string{
|
||||
LangGo: bindTypeGo,
|
||||
LangJava: bindTypeJava,
|
||||
}
|
||||
|
||||
// Helper function for the binding generators.
|
||||
// It reads the unmatched characters after the inner type-match,
|
||||
// (since the inner type is a prefix of the total type declaration),
|
||||
// looks for valid arrays (possibly a dynamic one) wrapping the inner type,
|
||||
// and returns the sizes of these arrays.
|
||||
//
|
||||
// Returned array sizes are in the same order as solidity signatures; inner array size first.
|
||||
// Array sizes may also be "", indicating a dynamic array.
|
||||
func wrapArray(stringKind string, innerLen int, innerMapping string) (string, []string) {
|
||||
remainder := stringKind[innerLen:]
|
||||
//find all the sizes
|
||||
matches := regexp.MustCompile(`\[(\d*)\]`).FindAllStringSubmatch(remainder, -1)
|
||||
parts := make([]string, 0, len(matches))
|
||||
for _, match := range matches {
|
||||
//get group 1 from the regex match
|
||||
parts = append(parts, match[1])
|
||||
}
|
||||
return innerMapping, parts
|
||||
}
|
||||
|
||||
// Translates the array sizes to a Go-lang declaration of a (nested) array of the inner type.
|
||||
// Simply returns the inner type if arraySizes is empty.
|
||||
func arrayBindingGo(inner string, arraySizes []string) string {
|
||||
out := ""
|
||||
//prepend all array sizes, from outer (end arraySizes) to inner (start arraySizes)
|
||||
for i := len(arraySizes) - 1; i >= 0; i-- {
|
||||
out += "[" + arraySizes[i] + "]"
|
||||
}
|
||||
out += inner
|
||||
return out
|
||||
}
|
||||
|
||||
// bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping
|
||||
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||
// mapped will use an upscaled type (e.g. *big.Int).
|
||||
func bindTypeGo(kind abi.Type) string {
|
||||
stringKind := kind.String()
|
||||
innerLen, innerMapping := bindUnnestedTypeGo(stringKind)
|
||||
return arrayBindingGo(wrapArray(stringKind, innerLen, innerMapping))
|
||||
}
|
||||
|
||||
// The inner function of bindTypeGo, this finds the inner type of stringKind.
|
||||
// (Or just the type itself if it is not an array or slice)
|
||||
// The length of the matched part is returned, with the the translated type.
|
||||
func bindUnnestedTypeGo(stringKind string) (int, string) {
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(stringKind, "address"):
|
||||
return len("address"), "common.Address"
|
||||
|
||||
case strings.HasPrefix(stringKind, "bytes"):
|
||||
parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind)
|
||||
return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1])
|
||||
|
||||
case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
|
||||
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind)
|
||||
switch parts[2] {
|
||||
case "8", "16", "32", "64":
|
||||
return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2])
|
||||
}
|
||||
return len(parts[0]), "*big.Int"
|
||||
|
||||
case strings.HasPrefix(stringKind, "bool"):
|
||||
return len("bool"), "bool"
|
||||
|
||||
case strings.HasPrefix(stringKind, "string"):
|
||||
return len("string"), "string"
|
||||
|
||||
default:
|
||||
return len(stringKind), stringKind
|
||||
}
|
||||
}
|
||||
|
||||
// Translates the array sizes to a Java declaration of a (nested) array of the inner type.
|
||||
// Simply returns the inner type if arraySizes is empty.
|
||||
func arrayBindingJava(inner string, arraySizes []string) string {
|
||||
// Java array type declarations do not include the length.
|
||||
return inner + strings.Repeat("[]", len(arraySizes))
|
||||
}
|
||||
|
||||
// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
|
||||
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
|
||||
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||
func bindTypeJava(kind abi.Type) string {
|
||||
stringKind := kind.String()
|
||||
innerLen, innerMapping := bindUnnestedTypeJava(stringKind)
|
||||
return arrayBindingJava(wrapArray(stringKind, innerLen, innerMapping))
|
||||
}
|
||||
|
||||
// The inner function of bindTypeJava, this finds the inner type of stringKind.
|
||||
// (Or just the type itself if it is not an array or slice)
|
||||
// The length of the matched part is returned, with the the translated type.
|
||||
func bindUnnestedTypeJava(stringKind string) (int, string) {
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(stringKind, "address"):
|
||||
parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
||||
if len(parts) != 2 {
|
||||
return len(stringKind), stringKind
|
||||
}
|
||||
if parts[1] == "" {
|
||||
return len("address"), "Address"
|
||||
}
|
||||
return len(parts[0]), "Addresses"
|
||||
|
||||
case strings.HasPrefix(stringKind, "bytes"):
|
||||
parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind)
|
||||
if len(parts) != 2 {
|
||||
return len(stringKind), stringKind
|
||||
}
|
||||
return len(parts[0]), "byte[]"
|
||||
|
||||
case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
|
||||
//Note that uint and int (without digits) are also matched,
|
||||
// these are size 256, and will translate to BigInt (the default).
|
||||
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind)
|
||||
if len(parts) != 3 {
|
||||
return len(stringKind), stringKind
|
||||
}
|
||||
|
||||
namedSize := map[string]string{
|
||||
"8": "byte",
|
||||
"16": "short",
|
||||
"32": "int",
|
||||
"64": "long",
|
||||
}[parts[2]]
|
||||
|
||||
//default to BigInt
|
||||
if namedSize == "" {
|
||||
namedSize = "BigInt"
|
||||
}
|
||||
return len(parts[0]), namedSize
|
||||
|
||||
case strings.HasPrefix(stringKind, "bool"):
|
||||
return len("bool"), "boolean"
|
||||
|
||||
case strings.HasPrefix(stringKind, "string"):
|
||||
return len("string"), "String"
|
||||
|
||||
default:
|
||||
return len(stringKind), stringKind
|
||||
}
|
||||
}
|
||||
|
||||
// bindTopicType is a set of type binders that convert Solidity types to some
|
||||
// supported programming language topic types.
|
||||
var bindTopicType = map[Lang]func(kind abi.Type) string{
|
||||
LangGo: bindTopicTypeGo,
|
||||
LangJava: bindTopicTypeJava,
|
||||
}
|
||||
|
||||
// bindTypeGo 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.
|
||||
func bindTopicTypeGo(kind abi.Type) string {
|
||||
bound := bindTypeGo(kind)
|
||||
if bound == "string" || bound == "[]byte" {
|
||||
bound = "common.Hash"
|
||||
}
|
||||
return bound
|
||||
}
|
||||
|
||||
// bindTypeGo 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.
|
||||
func bindTopicTypeJava(kind abi.Type) string {
|
||||
bound := bindTypeJava(kind)
|
||||
if bound == "String" || bound == "Bytes" {
|
||||
bound = "Hash"
|
||||
}
|
||||
return bound
|
||||
}
|
||||
|
||||
// namedType is a set of functions that transform language specific types to
|
||||
// named versions that my 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,
|
||||
}
|
||||
|
||||
// namedTypeJava converts some primitive data types to named variants that can
|
||||
// be used as parts of method names.
|
||||
func namedTypeJava(javaKind string, solKind abi.Type) string {
|
||||
switch javaKind {
|
||||
case "byte[]":
|
||||
return "Binary"
|
||||
case "byte[][]":
|
||||
return "Binaries"
|
||||
case "string":
|
||||
return "String"
|
||||
case "string[]":
|
||||
return "Strings"
|
||||
case "boolean":
|
||||
return "Bool"
|
||||
case "boolean[]":
|
||||
return "Bools"
|
||||
case "BigInt[]":
|
||||
return "BigInts"
|
||||
default:
|
||||
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
|
||||
if len(parts) != 4 {
|
||||
return javaKind
|
||||
}
|
||||
switch parts[2] {
|
||||
case "8", "16", "32", "64":
|
||||
if parts[3] == "" {
|
||||
return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2]))
|
||||
}
|
||||
return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2]))
|
||||
|
||||
default:
|
||||
return javaKind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// methodNormalizer is a name transformer that modifies Solidity method names to
|
||||
// conform to target language naming concentions.
|
||||
var methodNormalizer = map[Lang]func(string) string{
|
||||
LangGo: capitalise,
|
||||
LangJava: decapitalise,
|
||||
}
|
||||
|
||||
// capitalise makes a camel-case string which starts with an upper case character.
|
||||
func capitalise(input string) string {
|
||||
for len(input) > 0 && input[0] == '_' {
|
||||
input = input[1:]
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
return toCamelCase(strings.ToUpper(input[:1]) + input[1:])
|
||||
}
|
||||
|
||||
// decapitalise makes a camel-case string which starts with a lower case character.
|
||||
func decapitalise(input string) string {
|
||||
for len(input) > 0 && input[0] == '_' {
|
||||
input = input[1:]
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
return toCamelCase(strings.ToLower(input[:1]) + input[1:])
|
||||
}
|
||||
|
||||
// toCamelCase converts an under-score string to a camel-case string
|
||||
func toCamelCase(input string) string {
|
||||
toupper := false
|
||||
|
||||
result := ""
|
||||
for k, v := range input {
|
||||
switch {
|
||||
case k == 0:
|
||||
result = strings.ToUpper(string(input[0]))
|
||||
|
||||
case toupper:
|
||||
result += strings.ToUpper(string(v))
|
||||
toupper = false
|
||||
|
||||
case v == '_':
|
||||
toupper = true
|
||||
|
||||
default:
|
||||
result += string(v)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// structured checks whether a list of ABI data types has enough information to
|
||||
// operate through a proper Go struct or if flat returns are needed.
|
||||
func structured(args abi.Arguments) bool {
|
||||
if len(args) < 2 {
|
||||
return false
|
||||
}
|
||||
exists := make(map[string]bool)
|
||||
for _, out := range args {
|
||||
// If the name is anonymous, we can't organize into a struct
|
||||
if out.Name == "" {
|
||||
return false
|
||||
}
|
||||
// If the field name is empty when normalized or collides (var, Var, _var, _Var),
|
||||
// we can't organize into a struct
|
||||
field := capitalise(out.Name)
|
||||
if field == "" || exists[field] {
|
||||
return false
|
||||
}
|
||||
exists[field] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,522 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import "github.com/ethereum/go-ethereum/accounts/abi"
|
||||
|
||||
// tmplData is the data structure required to fill the binding template.
|
||||
type tmplData struct {
|
||||
Package string // Name of the package to place the generated file in
|
||||
Contracts map[string]*tmplContract // List of contracts to generate into this file
|
||||
}
|
||||
|
||||
// tmplContract contains the data needed to generate an individual contract binding.
|
||||
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
|
||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||
Events map[string]*tmplEvent // Contract events accessors
|
||||
}
|
||||
|
||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||
// and cached data fields.
|
||||
type tmplMethod struct {
|
||||
Original abi.Method // Original method as parsed by the abi package
|
||||
Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns)
|
||||
Structured bool // Whether the returns should be accumulated into a struct
|
||||
}
|
||||
|
||||
// tmplEvent is a wrapper around an a
|
||||
type tmplEvent struct {
|
||||
Original abi.Event // Original event as parsed by the abi package
|
||||
Normalized abi.Event // Normalized version of the parsed fields
|
||||
}
|
||||
|
||||
// tmplSource is language to template mapping containing all the supported
|
||||
// programming languages the package can generate to.
|
||||
var tmplSource = map[Lang]string{
|
||||
LangGo: tmplSourceGo,
|
||||
LangJava: tmplSourceJava,
|
||||
}
|
||||
|
||||
// tmplSourceGo is the Go source template use to generate the contract binding
|
||||
// based on.
|
||||
const tmplSourceGo = `
|
||||
// Code generated - DO NOT EDIT.
|
||||
// This file is a generated binding and any manual changes will be lost.
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
{{range $contract := .Contracts}}
|
||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||
const {{.Type}}ABI = "{{.InputABI}}"
|
||||
|
||||
{{if .InputBin}}
|
||||
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
||||
const {{.Type}}Bin = ` + "`" + `{{.InputBin}}` + "`" + `
|
||||
|
||||
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||
type {{.Type}} struct {
|
||||
{{.Type}}Caller // Read-only binding to the contract
|
||||
{{.Type}}Transactor // Write-only binding to the contract
|
||||
{{.Type}}Filterer // Log filterer for contract events
|
||||
}
|
||||
|
||||
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||
type {{.Type}}Caller struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||
type {{.Type}}Transactor struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||
type {{.Type}}Filterer struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||
// with pre-set call and transact options.
|
||||
type {{.Type}}Session struct {
|
||||
Contract *{{.Type}} // Generic contract binding to set the session for
|
||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||
}
|
||||
|
||||
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||
// with pre-set call options.
|
||||
type {{.Type}}CallerSession struct {
|
||||
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||
}
|
||||
|
||||
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||
// with pre-set transact options.
|
||||
type {{.Type}}TransactorSession struct {
|
||||
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||
}
|
||||
|
||||
// {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract.
|
||||
type {{.Type}}Raw struct {
|
||||
Contract *{{.Type}} // Generic contract binding to access the raw methods on
|
||||
}
|
||||
|
||||
// {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
||||
type {{.Type}}CallerRaw struct {
|
||||
Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on
|
||||
}
|
||||
|
||||
// {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
||||
type {{.Type}}TransactorRaw struct {
|
||||
Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on
|
||||
}
|
||||
|
||||
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
||||
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||
}
|
||||
|
||||
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
||||
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}Caller{contract: contract}, nil
|
||||
}
|
||||
|
||||
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
||||
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}Transactor{contract: contract}, nil
|
||||
}
|
||||
|
||||
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
||||
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}Filterer{contract: contract}, nil
|
||||
}
|
||||
|
||||
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// 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 {
|
||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.contract.Call(opts, result, method, params...)
|
||||
}
|
||||
|
||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts)
|
||||
}
|
||||
|
||||
// Transact invokes the (paid) contract method with params as input values.
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transact(opts, method, params...)
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// 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 {
|
||||
return _{{$contract.Type}}.Contract.contract.Call(opts, result, method, params...)
|
||||
}
|
||||
|
||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||
return _{{$contract.Type}}.Contract.contract.Transfer(opts)
|
||||
}
|
||||
|
||||
// Transact invokes the (paid) contract method with params as input values.
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
||||
}
|
||||
|
||||
{{range .Calls}}
|
||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}},{{end}}{{end}} error) {
|
||||
{{if .Structured}}ret := new(struct{
|
||||
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}}
|
||||
{{end}}
|
||||
}){{else}}var (
|
||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type}})
|
||||
{{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
|
||||
}
|
||||
|
||||
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{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: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type}},{{end}} {{end}} error) {
|
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range .Transacts}}
|
||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type}} {{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: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{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: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type}} {{end}}) (*types.Transaction, error) {
|
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range .Events}}
|
||||
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
||||
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
||||
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
|
||||
|
||||
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
||||
event string // Event name to use for unpacking event data
|
||||
|
||||
logs chan types.Log // Log channel receiving the found contract events
|
||||
sub ethereum.Subscription // Subscription for errors, completion and termination
|
||||
done bool // Whether the subscription completed delivering logs
|
||||
fail error // Occurred error to stop iteration
|
||||
}
|
||||
// Next advances the iterator to the subsequent event, returning whether there
|
||||
// are any more events found. In case of a retrieval or parsing error, false is
|
||||
// returned and Error() can be queried for the exact failure.
|
||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
|
||||
// If the iterator failed, stop iterating
|
||||
if (it.fail != nil) {
|
||||
return false
|
||||
}
|
||||
// If the iterator completed, deliver directly whatever's available
|
||||
if (it.done) {
|
||||
select {
|
||||
case log := <-it.logs:
|
||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||
it.fail = err
|
||||
return false
|
||||
}
|
||||
it.Event.Raw = log
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Iterator still in progress, wait for either a data or an error event
|
||||
select {
|
||||
case log := <-it.logs:
|
||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||
it.fail = err
|
||||
return false
|
||||
}
|
||||
it.Event.Raw = log
|
||||
return true
|
||||
|
||||
case err := <-it.sub.Err():
|
||||
it.done = true
|
||||
it.fail = err
|
||||
return it.Next()
|
||||
}
|
||||
}
|
||||
// Error returns any retrieval or parsing error occurred during filtering.
|
||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
||||
return it.fail
|
||||
}
|
||||
// Close terminates the iteration process, releasing any pending underlying
|
||||
// resources.
|
||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
||||
it.sub.Unsubscribe()
|
||||
return nil
|
||||
}
|
||||
|
||||
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type}}{{else}}{{bindtype .Type}}{{end}}; {{end}}
|
||||
Raw types.Log // Blockchain specific contextual infos
|
||||
}
|
||||
|
||||
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
||||
{{range .Normalized.Inputs}}
|
||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||
for _, {{.Name}}Item := range {{.Name}} {
|
||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||
}{{end}}{{end}}
|
||||
|
||||
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
||||
}
|
||||
|
||||
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// 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}}{{end}}{{end}}) (event.Subscription, error) {
|
||||
{{range .Normalized.Inputs}}
|
||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||
for _, {{.Name}}Item := range {{.Name}} {
|
||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||
}{{end}}{{end}}
|
||||
|
||||
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case log := <-logs:
|
||||
// New log arrived, parse the event and forward to the user
|
||||
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||
return err
|
||||
}
|
||||
event.Raw = log
|
||||
|
||||
select {
|
||||
case sink <- event:
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// tmplSourceJava is the Java source template use to generate the contract binding
|
||||
// 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!
|
||||
|
||||
package {{.Package}};
|
||||
|
||||
import org.ethereum.geth.*;
|
||||
import org.ethereum.geth.internal.*;
|
||||
|
||||
{{range $contract := .Contracts}}
|
||||
public class {{.Type}} {
|
||||
// ABI is the input ABI used to generate the binding from.
|
||||
public final static String ABI = "{{.InputABI}}";
|
||||
|
||||
{{if .InputBin}}
|
||||
// BYTECODE is the compiled bytecode used for deploying new contracts.
|
||||
public final static byte[] BYTECODE = "{{.InputBin}}".getBytes();
|
||||
|
||||
// deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||
public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception {
|
||||
Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}});
|
||||
{{range $index, $element := .Constructor.Inputs}}
|
||||
args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}});
|
||||
{{end}}
|
||||
return new {{.Type}}(Geth.deployContract(auth, ABI, BYTECODE, client, args));
|
||||
}
|
||||
|
||||
// Internal constructor used by contract deployment.
|
||||
private {{.Type}}(BoundContract deployment) {
|
||||
this.Address = deployment.getAddress();
|
||||
this.Deployer = deployment.getDeployer();
|
||||
this.Contract = deployment;
|
||||
}
|
||||
{{end}}
|
||||
|
||||
// Ethereum address where this contract is located at.
|
||||
public final Address Address;
|
||||
|
||||
// Ethereum transaction in which this contract was deployed (if known!).
|
||||
public final Transaction Deployer;
|
||||
|
||||
// Contract instance bound to a blockchain address.
|
||||
private final BoundContract Contract;
|
||||
|
||||
// Creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||
public {{.Type}}(Address address, EthereumClient client) throws Exception {
|
||||
this(Geth.bindContract(address, ABI, client));
|
||||
}
|
||||
|
||||
{{range .Calls}}
|
||||
{{if gt (len .Normalized.Outputs) 1}}
|
||||
// {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}.
|
||||
public class {{capitalise .Normalized.Name}}Results {
|
||||
{{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}};
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
// {{.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}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception {
|
||||
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
||||
{{range $index, $item := .Normalized.Inputs}}args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}});
|
||||
{{end}}
|
||||
|
||||
Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}});
|
||||
{{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type) .Type}}(); results.set({{$index}}, result{{$index}});
|
||||
{{end}}
|
||||
|
||||
if (opts == null) {
|
||||
opts = Geth.newCallOpts();
|
||||
}
|
||||
this.Contract.call(opts, results, "{{.Original.Name}}", args);
|
||||
{{if gt (len .Normalized.Outputs) 1}}
|
||||
{{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results();
|
||||
{{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type) .Type}}();
|
||||
{{end}}
|
||||
return result;
|
||||
{{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type) .Type}}();{{end}}
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range .Transacts}}
|
||||
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception {
|
||||
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
||||
{{range $index, $item := .Normalized.Inputs}}args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}});
|
||||
{{end}}
|
||||
|
||||
return this.Contract.transact(opts, "{{.Original.Name}}" , args);
|
||||
}
|
||||
{{end}}
|
||||
}
|
||||
{{end}}
|
||||
`
|
||||
@@ -1,189 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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) {
|
||||
topics := make([][]common.Hash, len(query))
|
||||
for i, filter := range query {
|
||||
for _, rule := range filter {
|
||||
var topic common.Hash
|
||||
|
||||
// Try to generate the topic based on simple types
|
||||
switch rule := rule.(type) {
|
||||
case common.Hash:
|
||||
copy(topic[:], rule[:])
|
||||
case common.Address:
|
||||
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
||||
case *big.Int:
|
||||
blob := rule.Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case bool:
|
||||
if rule {
|
||||
topic[common.HashLength-1] = 1
|
||||
}
|
||||
case int8:
|
||||
blob := big.NewInt(int64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case int16:
|
||||
blob := big.NewInt(int64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case int32:
|
||||
blob := big.NewInt(int64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case int64:
|
||||
blob := big.NewInt(rule).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint8:
|
||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint16:
|
||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint32:
|
||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint64:
|
||||
blob := new(big.Int).SetUint64(rule).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case string:
|
||||
hash := crypto.Keccak256Hash([]byte(rule))
|
||||
copy(topic[:], hash[:])
|
||||
case []byte:
|
||||
hash := crypto.Keccak256Hash(rule)
|
||||
copy(topic[:], hash[:])
|
||||
|
||||
default:
|
||||
// Attempt to generate the topic from funky types
|
||||
val := reflect.ValueOf(rule)
|
||||
|
||||
switch {
|
||||
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
|
||||
reflect.Copy(reflect.ValueOf(topic[common.HashLength-val.Len():]), val)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
|
||||
}
|
||||
}
|
||||
topics[i] = append(topics[i], topic)
|
||||
}
|
||||
}
|
||||
return topics, nil
|
||||
}
|
||||
|
||||
// Big batch of reflect types for topic reconstruction.
|
||||
var (
|
||||
reflectHash = reflect.TypeOf(common.Hash{})
|
||||
reflectAddress = reflect.TypeOf(common.Address{})
|
||||
reflectBigInt = reflect.TypeOf(new(big.Int))
|
||||
)
|
||||
|
||||
// parseTopics converts the indexed topic fields into actual log field values.
|
||||
//
|
||||
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
|
||||
// hashes as the topic value!
|
||||
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
|
||||
// Sanity check that the fields and topics match up
|
||||
if len(fields) != len(topics) {
|
||||
return errors.New("topic/field count mismatch")
|
||||
}
|
||||
// Iterate over all the fields and reconstruct them from topics
|
||||
for _, arg := range fields {
|
||||
if !arg.Indexed {
|
||||
return errors.New("non-indexed field in topic reconstruction")
|
||||
}
|
||||
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
|
||||
|
||||
// Try to parse the topic back into the fields based on primitive types
|
||||
switch field.Kind() {
|
||||
case reflect.Bool:
|
||||
if topics[0][common.HashLength-1] == 1 {
|
||||
field.Set(reflect.ValueOf(true))
|
||||
}
|
||||
case reflect.Int8:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(int8(num.Int64())))
|
||||
|
||||
case reflect.Int16:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(int16(num.Int64())))
|
||||
|
||||
case reflect.Int32:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(int32(num.Int64())))
|
||||
|
||||
case reflect.Int64:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(num.Int64()))
|
||||
|
||||
case reflect.Uint8:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(uint8(num.Uint64())))
|
||||
|
||||
case reflect.Uint16:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(uint16(num.Uint64())))
|
||||
|
||||
case reflect.Uint32:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(uint32(num.Uint64())))
|
||||
|
||||
case reflect.Uint64:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(num.Uint64()))
|
||||
|
||||
default:
|
||||
// Ran out of plain primitive types, try custom types
|
||||
switch field.Type() {
|
||||
case reflectHash: // Also covers all dynamic types
|
||||
field.Set(reflect.ValueOf(topics[0]))
|
||||
|
||||
case reflectAddress:
|
||||
var addr common.Address
|
||||
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
|
||||
field.Set(reflect.ValueOf(addr))
|
||||
|
||||
case reflectBigInt:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(num))
|
||||
|
||||
default:
|
||||
// Ran out of custom types, try the crazies
|
||||
switch {
|
||||
case arg.Type.T == abi.FixedBytesTy:
|
||||
reflect.Copy(field, reflect.ValueOf(topics[0][common.HashLength-arg.Type.Size:]))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
topics = topics[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,76 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// WaitMined waits for tx to be mined on the blockchain.
|
||||
// It stops waiting when the context is canceled.
|
||||
func WaitMined(ctx context.Context, b DeployBackend, tx *types.Transaction) (*types.Receipt, error) {
|
||||
queryTicker := time.NewTicker(time.Second)
|
||||
defer queryTicker.Stop()
|
||||
|
||||
logger := log.New("hash", tx.Hash())
|
||||
for {
|
||||
receipt, err := b.TransactionReceipt(ctx, tx.Hash())
|
||||
if receipt != nil {
|
||||
return receipt, nil
|
||||
}
|
||||
if err != nil {
|
||||
logger.Trace("Receipt retrieval failed", "err", err)
|
||||
} else {
|
||||
logger.Trace("Transaction not yet mined")
|
||||
}
|
||||
// Wait for the next round.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-queryTicker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WaitDeployed waits for a contract deployment transaction and returns the on-chain
|
||||
// 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")
|
||||
}
|
||||
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")
|
||||
}
|
||||
// Check that code has indeed been deployed at the address.
|
||||
// This matters on pre-Homestead chains: OOG in the constructor
|
||||
// could leave an empty account behind.
|
||||
code, err := b.CodeAt(ctx, receipt.ContractAddress, nil)
|
||||
if err == nil && len(code) == 0 {
|
||||
err = ErrNoCodeAfterDeploy
|
||||
}
|
||||
return receipt.ContractAddress, err
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
|
||||
var waitDeployedTests = map[string]struct {
|
||||
code string
|
||||
gas uint64
|
||||
wantAddress common.Address
|
||||
wantErr error
|
||||
}{
|
||||
"successful deploy": {
|
||||
code: `6060604052600a8060106000396000f360606040526008565b00`,
|
||||
gas: 3000000,
|
||||
wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"),
|
||||
},
|
||||
"empty code": {
|
||||
code: ``,
|
||||
gas: 300000,
|
||||
wantErr: bind.ErrNoCodeAfterDeploy,
|
||||
wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestWaitDeployed(t *testing.T) {
|
||||
for name, test := range waitDeployedTests {
|
||||
backend := backends.NewSimulatedBackend(core.GenesisAlloc{
|
||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
|
||||
})
|
||||
|
||||
// Create the transaction.
|
||||
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code))
|
||||
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||
|
||||
// Wait for it to get mined in the background.
|
||||
var (
|
||||
err error
|
||||
address common.Address
|
||||
mined = make(chan struct{})
|
||||
ctx = context.Background()
|
||||
)
|
||||
go func() {
|
||||
address, err = bind.WaitDeployed(ctx, backend, tx)
|
||||
close(mined)
|
||||
}()
|
||||
|
||||
// Send and mine the transaction.
|
||||
backend.SendTransaction(ctx, tx)
|
||||
backend.Commit()
|
||||
|
||||
select {
|
||||
case <-mined:
|
||||
if err != test.wantErr {
|
||||
t.Errorf("test %q: error mismatch: got %q, want %q", name, err, test.wantErr)
|
||||
}
|
||||
if address != test.wantAddress {
|
||||
t.Errorf("test %q: unexpected contract address %s", name, address.Hex())
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Errorf("test %q: timeout", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package abi implements the Ethereum ABI (Application Binary
|
||||
// Interface).
|
||||
//
|
||||
// The Ethereum ABI is strongly typed, known at compile time
|
||||
// and static. This ABI will handle basic type casting; unsigned
|
||||
// to signed and visa versa. It does not handle slice casting such
|
||||
// as unsigned slice to signed slice. Bit size type casting is also
|
||||
// handled. ints with a bit size of 32 will be properly cast to int256,
|
||||
// etc.
|
||||
package abi
|
||||
@@ -1,84 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var (
|
||||
errBadBool = errors.New("abi: improperly encoded boolean value")
|
||||
)
|
||||
|
||||
// formatSliceString formats the reflection kind with the given slice size
|
||||
// and returns a formatted string representation.
|
||||
func formatSliceString(kind reflect.Kind, sliceSize int) string {
|
||||
if sliceSize == -1 {
|
||||
return fmt.Sprintf("[]%v", kind)
|
||||
}
|
||||
return fmt.Sprintf("[%d]%v", sliceSize, kind)
|
||||
}
|
||||
|
||||
// sliceTypeCheck checks that the given slice can by assigned to the reflection
|
||||
// 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())
|
||||
}
|
||||
|
||||
if t.T == ArrayTy && val.Len() != t.Size {
|
||||
return typeErr(formatSliceString(t.Elem.Kind, t.Size), formatSliceString(val.Type().Elem().Kind(), val.Len()))
|
||||
}
|
||||
|
||||
if t.Elem.T == SliceTy {
|
||||
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())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeCheck checks that the given reflection value can be assigned to the reflection
|
||||
// type in t.
|
||||
func typeCheck(t Type, value reflect.Value) error {
|
||||
if t.T == SliceTy || t.T == ArrayTy {
|
||||
return sliceTypeCheck(t, value)
|
||||
}
|
||||
|
||||
// Check base type validity. Element types will be checked later on.
|
||||
if t.Kind != value.Kind() {
|
||||
return typeErr(t.Kind, value.Kind())
|
||||
} else if t.T == FixedBytesTy && t.Size != value.Len() {
|
||||
return typeErr(t.Type, value.Type())
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// typeErr returns a formatted type casting error.
|
||||
func typeErr(expected, got interface{}) error {
|
||||
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected)
|
||||
}
|
||||
@@ -1,57 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// Event is an event potentially triggered by the EVM's LOG mechanism. The Event
|
||||
// holds type information (inputs) about the yielded output. Anonymous events
|
||||
// don't get the signature canonical representation as the first LOG topic.
|
||||
type Event struct {
|
||||
Name string
|
||||
Anonymous bool
|
||||
Inputs Arguments
|
||||
}
|
||||
|
||||
func (e Event) String() string {
|
||||
inputs := make([]string, len(e.Inputs))
|
||||
for i, input := range e.Inputs {
|
||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
||||
if input.Indexed {
|
||||
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("e %v(%v)", e.Name, strings.Join(inputs, ", "))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
types := make([]string, len(e.Inputs))
|
||||
i := 0
|
||||
for _, input := range e.Inputs {
|
||||
types[i] = input.Type.String()
|
||||
i++
|
||||
}
|
||||
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
||||
}
|
||||
@@ -1,395 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var jsonEventTransfer = []byte(`{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true, "name": "from", "type": "address"
|
||||
}, {
|
||||
"indexed": true, "name": "to", "type": "address"
|
||||
}, {
|
||||
"indexed": false, "name": "value", "type": "uint256"
|
||||
}],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}`)
|
||||
|
||||
var jsonEventPledge = []byte(`{
|
||||
"anonymous": false,
|
||||
"inputs": [{
|
||||
"indexed": false, "name": "who", "type": "address"
|
||||
}, {
|
||||
"indexed": false, "name": "wad", "type": "uint128"
|
||||
}, {
|
||||
"indexed": false, "name": "currency", "type": "bytes3"
|
||||
}],
|
||||
"name": "Pledge",
|
||||
"type": "event"
|
||||
}`)
|
||||
|
||||
var jsonEventMixedCase = []byte(`{
|
||||
"anonymous": false,
|
||||
"inputs": [{
|
||||
"indexed": false, "name": "value", "type": "uint256"
|
||||
}, {
|
||||
"indexed": false, "name": "_value", "type": "uint256"
|
||||
}, {
|
||||
"indexed": false, "name": "Value", "type": "uint256"
|
||||
}],
|
||||
"name": "MixedCase",
|
||||
"type": "event"
|
||||
}`)
|
||||
|
||||
// 1000000
|
||||
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||
|
||||
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
// 1000000,2218516807680,1000001
|
||||
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||
|
||||
func TestEventId(t *testing.T) {
|
||||
var table = []struct {
|
||||
definition string
|
||||
expectations map[string]common.Hash
|
||||
}{
|
||||
{
|
||||
definition: `[
|
||||
{ "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||
{ "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
||||
]`,
|
||||
expectations: map[string]common.Hash{
|
||||
"balance": crypto.Keccak256Hash([]byte("balance(uint256)")),
|
||||
"check": crypto.Keccak256Hash([]byte("check(address,uint256)")),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
abi, err := JSON(strings.NewReader(test.definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
var i uint8 = 1
|
||||
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)
|
||||
}
|
||||
|
||||
func TestEventTupleUnpack(t *testing.T) {
|
||||
|
||||
type EventTransfer struct {
|
||||
Value *big.Int
|
||||
}
|
||||
|
||||
type EventTransferWithTag struct {
|
||||
// this is valid because `value` is not exportable,
|
||||
// so value is only unmarshalled into `Value1`.
|
||||
value *big.Int
|
||||
Value1 *big.Int `abi:"value"`
|
||||
}
|
||||
|
||||
type BadEventTransferWithSameFieldAndTag struct {
|
||||
Value *big.Int
|
||||
Value1 *big.Int `abi:"value"`
|
||||
}
|
||||
|
||||
type BadEventTransferWithDuplicatedTag struct {
|
||||
Value1 *big.Int `abi:"value"`
|
||||
Value2 *big.Int `abi:"value"`
|
||||
}
|
||||
|
||||
type BadEventTransferWithEmptyTag struct {
|
||||
Value *big.Int `abi:""`
|
||||
}
|
||||
|
||||
type EventPledge struct {
|
||||
Who common.Address
|
||||
Wad *big.Int
|
||||
Currency [3]byte
|
||||
}
|
||||
|
||||
type BadEventPledge struct {
|
||||
Who string
|
||||
Wad int
|
||||
Currency [3]byte
|
||||
}
|
||||
|
||||
type EventMixedCase struct {
|
||||
Value1 *big.Int `abi:"value"`
|
||||
Value2 *big.Int `abi:"_value"`
|
||||
Value3 *big.Int `abi:"Value"`
|
||||
}
|
||||
|
||||
bigint := new(big.Int)
|
||||
bigintExpected := big.NewInt(1000000)
|
||||
bigintExpected2 := big.NewInt(2218516807680)
|
||||
bigintExpected3 := big.NewInt(1000001)
|
||||
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||
var testCases = []struct {
|
||||
data string
|
||||
dest interface{}
|
||||
expected interface{}
|
||||
jsonLog []byte
|
||||
error string
|
||||
name string
|
||||
}{{
|
||||
transferData1,
|
||||
&EventTransfer{},
|
||||
&EventTransfer{Value: bigintExpected},
|
||||
jsonEventTransfer,
|
||||
"",
|
||||
"Can unpack ERC20 Transfer event into structure",
|
||||
}, {
|
||||
transferData1,
|
||||
&[]interface{}{&bigint},
|
||||
&[]interface{}{&bigintExpected},
|
||||
jsonEventTransfer,
|
||||
"",
|
||||
"Can unpack ERC20 Transfer event into slice",
|
||||
}, {
|
||||
transferData1,
|
||||
&EventTransferWithTag{},
|
||||
&EventTransferWithTag{Value1: bigintExpected},
|
||||
jsonEventTransfer,
|
||||
"",
|
||||
"Can unpack ERC20 Transfer event into structure with abi: tag",
|
||||
}, {
|
||||
transferData1,
|
||||
&BadEventTransferWithDuplicatedTag{},
|
||||
&BadEventTransferWithDuplicatedTag{},
|
||||
jsonEventTransfer,
|
||||
"struct: abi tag in 'Value2' already mapped",
|
||||
"Can not unpack ERC20 Transfer event with duplicated abi tag",
|
||||
}, {
|
||||
transferData1,
|
||||
&BadEventTransferWithSameFieldAndTag{},
|
||||
&BadEventTransferWithSameFieldAndTag{},
|
||||
jsonEventTransfer,
|
||||
"abi: multiple variables maps to the same abi field 'value'",
|
||||
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
|
||||
}, {
|
||||
transferData1,
|
||||
&BadEventTransferWithEmptyTag{},
|
||||
&BadEventTransferWithEmptyTag{},
|
||||
jsonEventTransfer,
|
||||
"struct: abi tag in 'Value' is empty",
|
||||
"Can not unpack ERC20 Transfer event with an empty tag",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&EventPledge{},
|
||||
&EventPledge{
|
||||
addr,
|
||||
bigintExpected2,
|
||||
[3]byte{'u', 's', 'd'}},
|
||||
jsonEventPledge,
|
||||
"",
|
||||
"Can unpack Pledge event into structure",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||
&[]interface{}{
|
||||
&addr,
|
||||
&bigintExpected2,
|
||||
&[3]byte{'u', 's', 'd'}},
|
||||
jsonEventPledge,
|
||||
"",
|
||||
"Can unpack Pledge event into slice",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[3]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||
&[3]interface{}{
|
||||
&addr,
|
||||
&bigintExpected2,
|
||||
&[3]byte{'u', 's', 'd'}},
|
||||
jsonEventPledge,
|
||||
"",
|
||||
"Can unpack Pledge event into an array",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[]interface{}{new(int), 0, 0},
|
||||
&[]interface{}{},
|
||||
jsonEventPledge,
|
||||
"abi: cannot unmarshal common.Address in to int",
|
||||
"Can not unpack Pledge event into slice with wrong types",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&BadEventPledge{},
|
||||
&BadEventPledge{},
|
||||
jsonEventPledge,
|
||||
"abi: cannot unmarshal common.Address in to string",
|
||||
"Can not unpack Pledge event into struct with wrong filed types",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[]interface{}{common.Address{}, new(big.Int)},
|
||||
&[]interface{}{},
|
||||
jsonEventPledge,
|
||||
"abi: insufficient number of elements in the list/array 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 {}",
|
||||
"Can not unpack Pledge event into map",
|
||||
}, {
|
||||
mixedCaseData1,
|
||||
&EventMixedCase{},
|
||||
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
|
||||
jsonEventMixedCase,
|
||||
"",
|
||||
"Can unpack abi variables with mixed case",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert := assert.New(t)
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
||||
if tc.error == "" {
|
||||
assert.Nil(err, "Should be able to unpack event data.")
|
||||
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||
} else {
|
||||
assert.EqualError(err, tc.error, tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error {
|
||||
data, err := hex.DecodeString(hexData)
|
||||
assert.NoError(err, "Hex data should be a correct hex-string")
|
||||
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)
|
||||
}
|
||||
|
||||
/*
|
||||
Taken from
|
||||
https://github.com/ethereum/go-ethereum/pull/15568
|
||||
*/
|
||||
|
||||
type testResult struct {
|
||||
Values [2]*big.Int
|
||||
Value1 *big.Int
|
||||
Value2 *big.Int
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
definition string
|
||||
want testResult
|
||||
}
|
||||
|
||||
func (tc testCase) encoded(intType, arrayType Type) []byte {
|
||||
var b bytes.Buffer
|
||||
if tc.want.Value1 != nil {
|
||||
val, _ := intType.pack(reflect.ValueOf(tc.want.Value1))
|
||||
b.Write(val)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.want.Values, [2]*big.Int{nil, nil}) {
|
||||
val, _ := arrayType.pack(reflect.ValueOf(tc.want.Values))
|
||||
b.Write(val)
|
||||
}
|
||||
if tc.want.Value2 != nil {
|
||||
val, _ := intType.pack(reflect.ValueOf(tc.want.Value2))
|
||||
b.Write(val)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// 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
|
||||
Value2 uint8
|
||||
}
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
require.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
b.Write(packNum(reflect.ValueOf(uint8(8))))
|
||||
var rst testStruct
|
||||
require.NoError(t, abi.Unpack(&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.
|
||||
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
|
||||
Value2 string
|
||||
}
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
require.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
stringOut := "abc"
|
||||
// number of fields that will be encoded * 32
|
||||
b.Write(packNum(reflect.ValueOf(32)))
|
||||
b.Write(packNum(reflect.ValueOf(len(stringOut))))
|
||||
b.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||
|
||||
var rst testStruct
|
||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
|
||||
require.Equal(t, stringOut, rst.Value2)
|
||||
}
|
||||
@@ -1,79 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// 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 therefor requires no Tx to be send to the
|
||||
// network. A method such as `Transact` does require a Tx and thus will
|
||||
// be flagged `true`.
|
||||
// Input specifies the required input parameters for this gives method.
|
||||
type Method struct {
|
||||
Name string
|
||||
Const bool
|
||||
Inputs Arguments
|
||||
Outputs Arguments
|
||||
}
|
||||
|
||||
// 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 {
|
||||
types := make([]string, len(method.Inputs))
|
||||
i := 0
|
||||
for _, input := range method.Inputs {
|
||||
types[i] = input.Type.String()
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf("%v(%v)", method.Name, 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.Name, input.Type)
|
||||
}
|
||||
outputs := make([]string, len(method.Outputs))
|
||||
for i, output := range method.Outputs {
|
||||
if len(output.Name) > 0 {
|
||||
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
||||
}
|
||||
outputs[i] += output.Type.String()
|
||||
}
|
||||
constant := ""
|
||||
if method.Const {
|
||||
constant = "constant "
|
||||
}
|
||||
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||
}
|
||||
|
||||
func (method Method) Id() []byte {
|
||||
return crypto.Keccak256([]byte(method.Sig()))[:4]
|
||||
}
|
||||
@@ -1,44 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"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{})
|
||||
)
|
||||
|
||||
// U256 converts a big Int into a 256bit EVM number.
|
||||
func U256(n *big.Int) []byte {
|
||||
return math.PaddedBigBytes(math.U256(n), 32)
|
||||
}
|
||||
@@ -1,33 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNumberTypes(t *testing.T) {
|
||||
ubytes := make([]byte, 32)
|
||||
ubytes[31] = 1
|
||||
|
||||
unsigned := U256(big.NewInt(1))
|
||||
if !bytes.Equal(unsigned, ubytes) {
|
||||
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||
}
|
||||
}
|
||||
@@ -1,81 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
)
|
||||
|
||||
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
|
||||
// bytes slice
|
||||
func packBytesSlice(bytes []byte, l int) []byte {
|
||||
len := packNum(reflect.ValueOf(l))
|
||||
return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...)
|
||||
}
|
||||
|
||||
// packElement packs the given reflect value according to the abi specification in
|
||||
// t.
|
||||
func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||
switch t.T {
|
||||
case IntTy, UintTy:
|
||||
return packNum(reflectValue)
|
||||
case StringTy:
|
||||
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len())
|
||||
case AddressTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
}
|
||||
|
||||
return common.LeftPadBytes(reflectValue.Bytes(), 32)
|
||||
case BoolTy:
|
||||
if reflectValue.Bool() {
|
||||
return math.PaddedBigBytes(common.Big1, 32)
|
||||
}
|
||||
return math.PaddedBigBytes(common.Big0, 32)
|
||||
case BytesTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
}
|
||||
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len())
|
||||
case FixedBytesTy, FunctionTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
}
|
||||
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
||||
default:
|
||||
panic("abi: fatal error")
|
||||
}
|
||||
}
|
||||
|
||||
// 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()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return U256(big.NewInt(value.Int()))
|
||||
case reflect.Ptr:
|
||||
return U256(value.Interface().(*big.Int))
|
||||
default:
|
||||
panic("abi: fatal error")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,443 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
typ string
|
||||
|
||||
input interface{}
|
||||
output []byte
|
||||
}{
|
||||
{
|
||||
"uint8",
|
||||
uint8(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint8[]",
|
||||
[]uint8{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint16",
|
||||
uint16(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint16[]",
|
||||
[]uint16{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint32",
|
||||
uint32(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint32[]",
|
||||
[]uint32{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint64",
|
||||
uint64(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint64[]",
|
||||
[]uint64{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint256",
|
||||
big.NewInt(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"uint256[]",
|
||||
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int8",
|
||||
int8(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int8[]",
|
||||
[]int8{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int16",
|
||||
int16(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int16[]",
|
||||
[]int16{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int32",
|
||||
int32(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int32[]",
|
||||
[]int32{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int64",
|
||||
int64(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int64[]",
|
||||
[]int64{1, 2},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int256",
|
||||
big.NewInt(2),
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"int256[]",
|
||||
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||
},
|
||||
{
|
||||
"bytes1",
|
||||
[1]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes2",
|
||||
[2]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes3",
|
||||
[3]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes4",
|
||||
[4]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes5",
|
||||
[5]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes6",
|
||||
[6]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes7",
|
||||
[7]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes8",
|
||||
[8]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes9",
|
||||
[9]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes10",
|
||||
[10]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes11",
|
||||
[11]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes12",
|
||||
[12]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes13",
|
||||
[13]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes14",
|
||||
[14]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes15",
|
||||
[15]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes16",
|
||||
[16]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes17",
|
||||
[17]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes18",
|
||||
[18]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes19",
|
||||
[19]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes20",
|
||||
[20]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes21",
|
||||
[21]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes22",
|
||||
[22]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes23",
|
||||
[23]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes24",
|
||||
[24]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes24",
|
||||
[24]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes25",
|
||||
[25]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes26",
|
||||
[26]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes27",
|
||||
[27]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes28",
|
||||
[28]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes29",
|
||||
[29]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes30",
|
||||
[30]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes31",
|
||||
[31]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes32",
|
||||
[32]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"uint32[2][3][4]",
|
||||
[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[]",
|
||||
[]common.Address{{1}, {2}},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"bytes32[]",
|
||||
[]common.Hash{{1}, {2}},
|
||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"function",
|
||||
[24]byte{1},
|
||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
{
|
||||
"string",
|
||||
"foobar",
|
||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
|
||||
},
|
||||
} {
|
||||
typ, err := NewType(test.typ)
|
||||
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("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethodPack(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig := abi.Methods["slice"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
|
||||
packed, err := abi.Pack("slice", []uint32{1, 2})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
var addrA, addrB = common.Address{1}, common.Address{2}
|
||||
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)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||
|
||||
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
var addrC, addrD = common.Address{3}, common.Address{4}
|
||||
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)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||
|
||||
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
sig = abi.Methods["slice256"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
|
||||
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
value reflect.Value
|
||||
packed []byte
|
||||
}{
|
||||
// Protocol limits
|
||||
{reflect.ValueOf(0), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")},
|
||||
{reflect.ValueOf(1), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||
{reflect.ValueOf(-1), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")},
|
||||
|
||||
// Type corner cases
|
||||
{reflect.ValueOf(uint8(math.MaxUint8)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")},
|
||||
{reflect.ValueOf(uint16(math.MaxUint16)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000ffff")},
|
||||
{reflect.ValueOf(uint32(math.MaxUint32)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000ffffffff")},
|
||||
{reflect.ValueOf(uint64(math.MaxUint64)), common.Hex2Bytes("000000000000000000000000000000000000000000000000ffffffffffffffff")},
|
||||
|
||||
{reflect.ValueOf(int8(math.MaxInt8)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000007f")},
|
||||
{reflect.ValueOf(int16(math.MaxInt16)), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000007fff")},
|
||||
{reflect.ValueOf(int32(math.MaxInt32)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000007fffffff")},
|
||||
{reflect.ValueOf(int64(math.MaxInt64)), common.Hex2Bytes("0000000000000000000000000000000000000000000000007fffffffffffffff")},
|
||||
|
||||
{reflect.ValueOf(int8(math.MinInt8)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80")},
|
||||
{reflect.ValueOf(int16(math.MinInt16)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000")},
|
||||
{reflect.ValueOf(int32(math.MinInt32)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000")},
|
||||
{reflect.ValueOf(int64(math.MinInt64)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
packed := packNum(tt.value)
|
||||
if !bytes.Equal(packed, tt.packed) {
|
||||
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,212 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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())
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// reflectIntKind returns the reflect using the given size and
|
||||
// unsignedness.
|
||||
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) {
|
||||
switch size {
|
||||
case 8:
|
||||
if unsigned {
|
||||
return reflect.Uint8, uint8T
|
||||
}
|
||||
return reflect.Int8, int8T
|
||||
case 16:
|
||||
if unsigned {
|
||||
return reflect.Uint16, uint16T
|
||||
}
|
||||
return reflect.Int16, int16T
|
||||
case 32:
|
||||
if unsigned {
|
||||
return reflect.Uint32, uint32T
|
||||
}
|
||||
return reflect.Int32, int32T
|
||||
case 64:
|
||||
if unsigned {
|
||||
return reflect.Uint64, uint64T
|
||||
}
|
||||
return reflect.Int64, int64T
|
||||
}
|
||||
return reflect.Ptr, bigT
|
||||
}
|
||||
|
||||
// mustArrayToBytesSlice 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())
|
||||
reflect.Copy(slice, value)
|
||||
return slice
|
||||
}
|
||||
|
||||
// set attempts to assign src to dst by either setting, copying or otherwise.
|
||||
//
|
||||
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
||||
// strict ruleset as bare `reflect` does.
|
||||
func set(dst, src reflect.Value, output Argument) error {
|
||||
dstType := dst.Type()
|
||||
srcType := src.Type()
|
||||
switch {
|
||||
case dstType.AssignableTo(srcType):
|
||||
dst.Set(src)
|
||||
case dstType.Kind() == reflect.Interface:
|
||||
dst.Set(src)
|
||||
case dstType.Kind() == reflect.Ptr:
|
||||
return set(dst.Elem(), src, output)
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mapAbiToStringField maps abi to struct fields.
|
||||
// first round: for each Exportable field that contains a `abi:""` tag
|
||||
// and this field name exists in the arguments, pair them together.
|
||||
// second round: for each argument field that has not been already linked,
|
||||
// find what variable is expected to be mapped into, if it exists and has not been
|
||||
// used, pair them.
|
||||
func mapAbiToStructFields(args Arguments, value reflect.Value) (map[string]string, error) {
|
||||
|
||||
typ := value.Type()
|
||||
|
||||
abi2struct := make(map[string]string)
|
||||
struct2abi := make(map[string]string)
|
||||
|
||||
// first round ~~~
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
structFieldName := typ.Field(i).Name
|
||||
|
||||
// skip private struct fields.
|
||||
if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip fields that have no abi:"" tag.
|
||||
var ok bool
|
||||
var tagName string
|
||||
if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if tag is empty.
|
||||
if tagName == "" {
|
||||
return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName)
|
||||
}
|
||||
|
||||
// check which argument field matches with the abi tag.
|
||||
found := false
|
||||
for _, abiField := range args.NonIndexed() {
|
||||
if abiField.Name == tagName {
|
||||
if abi2struct[abiField.Name] != "" {
|
||||
return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
|
||||
}
|
||||
// pair them
|
||||
abi2struct[abiField.Name] = structFieldName
|
||||
struct2abi[structFieldName] = abiField.Name
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
// check if this tag has been mapped.
|
||||
if !found {
|
||||
return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// second round ~~~
|
||||
for _, arg := range args {
|
||||
|
||||
abiFieldName := arg.Name
|
||||
structFieldName := capitalise(abiFieldName)
|
||||
|
||||
if structFieldName == "" {
|
||||
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
||||
}
|
||||
|
||||
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
||||
// struct field with the same field name. If so, raise an error:
|
||||
// abi: [ { "name": "value" } ]
|
||||
// struct { Value *big.Int , Value1 *big.Int `abi:"value"`}
|
||||
if abi2struct[abiFieldName] != "" {
|
||||
if abi2struct[abiFieldName] != structFieldName &&
|
||||
struct2abi[structFieldName] == "" &&
|
||||
value.FieldByName(structFieldName).IsValid() {
|
||||
return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", abiFieldName)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// return an error if this struct field has already been paired.
|
||||
if struct2abi[structFieldName] != "" {
|
||||
return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName)
|
||||
}
|
||||
|
||||
if value.FieldByName(structFieldName).IsValid() {
|
||||
// pair them
|
||||
abi2struct[abiFieldName] = structFieldName
|
||||
struct2abi[structFieldName] = abiFieldName
|
||||
} else {
|
||||
// not paired, but annotate as used, to detect cases like
|
||||
// abi : [ { "name": "value" }, { "name": "_value" } ]
|
||||
// struct { Value *big.Int }
|
||||
struct2abi[structFieldName] = abiFieldName
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return abi2struct, nil
|
||||
}
|
||||
@@ -1,204 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Type enumerator
|
||||
const (
|
||||
IntTy byte = iota
|
||||
UintTy
|
||||
BoolTy
|
||||
StringTy
|
||||
SliceTy
|
||||
ArrayTy
|
||||
AddressTy
|
||||
FixedBytesTy
|
||||
BytesTy
|
||||
HashTy
|
||||
FixedPointTy
|
||||
FunctionTy
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var (
|
||||
// typeRegex parses the abi sub types
|
||||
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
||||
)
|
||||
|
||||
// NewType creates a new reflection type of abi type given in t.
|
||||
func NewType(t string) (typ Type, err error) {
|
||||
// check that array brackets are equal if they exist
|
||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||
return Type{}, fmt.Errorf("invalid arg type in abi")
|
||||
}
|
||||
|
||||
typ.stringKind = t
|
||||
|
||||
// if there are brackets, get ready to go into slice/array mode and
|
||||
// recursively create the type
|
||||
if strings.Count(t, "[") != 0 {
|
||||
i := strings.LastIndex(t, "[")
|
||||
// recursively embed the type
|
||||
embeddedType, err := NewType(t[:i])
|
||||
if err != nil {
|
||||
return Type{}, err
|
||||
}
|
||||
// grab the last cell and create a type from there
|
||||
sliced := t[i:]
|
||||
// grab the slice size with regexp
|
||||
re := regexp.MustCompile("[0-9]+")
|
||||
intz := re.FindAllString(sliced, -1)
|
||||
|
||||
if len(intz) == 0 {
|
||||
// is a slice
|
||||
typ.T = SliceTy
|
||||
typ.Kind = reflect.Slice
|
||||
typ.Elem = &embeddedType
|
||||
typ.Type = reflect.SliceOf(embeddedType.Type)
|
||||
} else if len(intz) == 1 {
|
||||
// is a 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)
|
||||
} else {
|
||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||
}
|
||||
return typ, err
|
||||
}
|
||||
// parse the type and size of the abi-type.
|
||||
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||
// varSize is the size of the variable
|
||||
var varSize int
|
||||
if len(parsedType[3]) > 0 {
|
||||
var err error
|
||||
varSize, err = strconv.Atoi(parsedType[2])
|
||||
if err != nil {
|
||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||
}
|
||||
} else {
|
||||
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||
// this should fail because it means that there's something wrong with
|
||||
// the abi type (the compiler should always format it to the size...always)
|
||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||
}
|
||||
}
|
||||
// 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 "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)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// String implements Stringer
|
||||
func (t Type) String() (out string) {
|
||||
return t.stringKind
|
||||
}
|
||||
|
||||
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||
// dereference pointer first if it's a pointer
|
||||
v = indirect(v)
|
||||
|
||||
if err := typeCheck(t, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.T == SliceTy || t.T == ArrayTy {
|
||||
var packed []byte
|
||||
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
val, err := t.Elem.pack(v.Index(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packed = append(packed, val...)
|
||||
}
|
||||
if t.T == SliceTy {
|
||||
return packBytesSlice(packed, v.Len()), nil
|
||||
} else if t.T == ArrayTy {
|
||||
return packed, nil
|
||||
}
|
||||
}
|
||||
return packElement(t, v), nil
|
||||
}
|
||||
|
||||
// requireLengthPrefix returns whether the type requires any sort of length
|
||||
// prefixing.
|
||||
func (t Type) requiresLengthPrefix() bool {
|
||||
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||
}
|
||||
@@ -1,283 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
||||
// the stringer interface to allow printing type details in the tests below.
|
||||
type typeWithoutStringer Type
|
||||
|
||||
// Tests that all allowed types get recognized by the type parser.
|
||||
func TestTypeRegexp(t *testing.T) {
|
||||
tests := []struct {
|
||||
blob string
|
||||
kind Type
|
||||
}{
|
||||
{"bool", Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
|
||||
{"bool[]", 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]", 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][]", 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[][]", 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]", 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]", 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]", 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]", 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[][][]", 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][]", 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", Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}},
|
||||
{"int16", Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}},
|
||||
{"int32", Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}},
|
||||
{"int64", Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}},
|
||||
{"int256", Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}},
|
||||
{"int8[]", 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]", 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[]", 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]", 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[]", 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]", 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[]", 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]", 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[]", 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]", 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", Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||
{"uint16", Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||
{"uint32", Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||
{"uint64", Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||
{"uint256", Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}},
|
||||
{"uint8[]", 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]", 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[]", 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]", 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[]", 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]", 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[]", 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]", 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[]", 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]", 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", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
||||
{"bytes[]", 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]", 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[]", 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]", 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", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
||||
{"string[]", 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]", 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", Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}},
|
||||
{"address[]", 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]", 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]"}},
|
||||
// TODO when fixed types are implemented properly
|
||||
// {"fixed", Type{}},
|
||||
// {"fixed128x128", Type{}},
|
||||
// {"fixed[]", Type{}},
|
||||
// {"fixed[2]", Type{}},
|
||||
// {"fixed128x128[]", Type{}},
|
||||
// {"fixed128x128[2]", Type{}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
typ, err := NewType(tt.blob)
|
||||
if err != nil {
|
||||
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
||||
}
|
||||
if !reflect.DeepEqual(typ, tt.kind) {
|
||||
t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeCheck(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
typ string
|
||||
input interface{}
|
||||
err string
|
||||
}{
|
||||
{"uint", big.NewInt(1), "unsupported arg type: uint"},
|
||||
{"int", big.NewInt(1), "unsupported arg type: int"},
|
||||
{"uint256", big.NewInt(1), ""},
|
||||
{"uint256[][3][]", [][3][]*big.Int{{{}}}, ""},
|
||||
{"uint256[][][3]", [3][][]*big.Int{{{}}}, ""},
|
||||
{"uint256[3][][]", [][][3]*big.Int{{{}}}, ""},
|
||||
{"uint256[3][3][3]", [3][3][3]*big.Int{{{}}}, ""},
|
||||
{"uint8[][]", [][]uint8{}, ""},
|
||||
{"int256", big.NewInt(1), ""},
|
||||
{"uint8", uint8(1), ""},
|
||||
{"uint16", uint16(1), ""},
|
||||
{"uint32", uint32(1), ""},
|
||||
{"uint64", uint64(1), ""},
|
||||
{"int8", int8(1), ""},
|
||||
{"int16", int16(1), ""},
|
||||
{"int32", int32(1), ""},
|
||||
{"int64", int64(1), ""},
|
||||
{"uint24", big.NewInt(1), ""},
|
||||
{"uint40", big.NewInt(1), ""},
|
||||
{"uint48", big.NewInt(1), ""},
|
||||
{"uint56", big.NewInt(1), ""},
|
||||
{"uint72", big.NewInt(1), ""},
|
||||
{"uint80", big.NewInt(1), ""},
|
||||
{"uint88", big.NewInt(1), ""},
|
||||
{"uint96", big.NewInt(1), ""},
|
||||
{"uint104", big.NewInt(1), ""},
|
||||
{"uint112", big.NewInt(1), ""},
|
||||
{"uint120", big.NewInt(1), ""},
|
||||
{"uint128", big.NewInt(1), ""},
|
||||
{"uint136", big.NewInt(1), ""},
|
||||
{"uint144", big.NewInt(1), ""},
|
||||
{"uint152", big.NewInt(1), ""},
|
||||
{"uint160", big.NewInt(1), ""},
|
||||
{"uint168", big.NewInt(1), ""},
|
||||
{"uint176", big.NewInt(1), ""},
|
||||
{"uint184", big.NewInt(1), ""},
|
||||
{"uint192", big.NewInt(1), ""},
|
||||
{"uint200", big.NewInt(1), ""},
|
||||
{"uint208", big.NewInt(1), ""},
|
||||
{"uint216", big.NewInt(1), ""},
|
||||
{"uint224", big.NewInt(1), ""},
|
||||
{"uint232", big.NewInt(1), ""},
|
||||
{"uint240", big.NewInt(1), ""},
|
||||
{"uint248", big.NewInt(1), ""},
|
||||
{"int24", big.NewInt(1), ""},
|
||||
{"int40", big.NewInt(1), ""},
|
||||
{"int48", big.NewInt(1), ""},
|
||||
{"int56", big.NewInt(1), ""},
|
||||
{"int72", big.NewInt(1), ""},
|
||||
{"int80", big.NewInt(1), ""},
|
||||
{"int88", big.NewInt(1), ""},
|
||||
{"int96", big.NewInt(1), ""},
|
||||
{"int104", big.NewInt(1), ""},
|
||||
{"int112", big.NewInt(1), ""},
|
||||
{"int120", big.NewInt(1), ""},
|
||||
{"int128", big.NewInt(1), ""},
|
||||
{"int136", big.NewInt(1), ""},
|
||||
{"int144", big.NewInt(1), ""},
|
||||
{"int152", big.NewInt(1), ""},
|
||||
{"int160", big.NewInt(1), ""},
|
||||
{"int168", big.NewInt(1), ""},
|
||||
{"int176", big.NewInt(1), ""},
|
||||
{"int184", big.NewInt(1), ""},
|
||||
{"int192", big.NewInt(1), ""},
|
||||
{"int200", big.NewInt(1), ""},
|
||||
{"int208", big.NewInt(1), ""},
|
||||
{"int216", big.NewInt(1), ""},
|
||||
{"int224", big.NewInt(1), ""},
|
||||
{"int232", big.NewInt(1), ""},
|
||||
{"int240", big.NewInt(1), ""},
|
||||
{"int248", big.NewInt(1), ""},
|
||||
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||
{"uint8", uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
||||
{"uint8", uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
||||
{"uint8", uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
||||
{"uint8", int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
||||
{"uint8", int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
||||
{"uint8", int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
||||
{"uint8", int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
||||
{"uint16", uint16(1), ""},
|
||||
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||
{"uint16[]", []uint16{1, 2, 3}, ""},
|
||||
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
||||
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
|
||||
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
||||
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
||||
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||
{"address[]", []common.Address{{1}}, ""},
|
||||
{"address[1]", []common.Address{{1}}, ""},
|
||||
{"address[1]", [1]common.Address{{1}}, ""},
|
||||
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||
{"bytes32", [32]byte{}, ""},
|
||||
{"bytes31", [31]byte{}, ""},
|
||||
{"bytes30", [30]byte{}, ""},
|
||||
{"bytes29", [29]byte{}, ""},
|
||||
{"bytes28", [28]byte{}, ""},
|
||||
{"bytes27", [27]byte{}, ""},
|
||||
{"bytes26", [26]byte{}, ""},
|
||||
{"bytes25", [25]byte{}, ""},
|
||||
{"bytes24", [24]byte{}, ""},
|
||||
{"bytes23", [23]byte{}, ""},
|
||||
{"bytes22", [22]byte{}, ""},
|
||||
{"bytes21", [21]byte{}, ""},
|
||||
{"bytes20", [20]byte{}, ""},
|
||||
{"bytes19", [19]byte{}, ""},
|
||||
{"bytes18", [18]byte{}, ""},
|
||||
{"bytes17", [17]byte{}, ""},
|
||||
{"bytes16", [16]byte{}, ""},
|
||||
{"bytes15", [15]byte{}, ""},
|
||||
{"bytes14", [14]byte{}, ""},
|
||||
{"bytes13", [13]byte{}, ""},
|
||||
{"bytes12", [12]byte{}, ""},
|
||||
{"bytes11", [11]byte{}, ""},
|
||||
{"bytes10", [10]byte{}, ""},
|
||||
{"bytes9", [9]byte{}, ""},
|
||||
{"bytes8", [8]byte{}, ""},
|
||||
{"bytes7", [7]byte{}, ""},
|
||||
{"bytes6", [6]byte{}, ""},
|
||||
{"bytes5", [5]byte{}, ""},
|
||||
{"bytes4", [4]byte{}, ""},
|
||||
{"bytes3", [3]byte{}, ""},
|
||||
{"bytes2", [2]byte{}, ""},
|
||||
{"bytes1", [1]byte{}, ""},
|
||||
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||
{"bytes32", common.Hash{1}, ""},
|
||||
{"bytes31", common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
|
||||
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||
{"bytes", []byte{0, 1}, ""},
|
||||
{"bytes", [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
|
||||
{"bytes", common.Hash{1}, "abi: cannot use array as type slice as argument"},
|
||||
{"string", "hello world", ""},
|
||||
{"string", string(""), ""},
|
||||
{"string", []byte{}, "abi: cannot use slice as type string as argument"},
|
||||
{"bytes32[]", [][32]byte{{}}, ""},
|
||||
{"function", [24]byte{}, ""},
|
||||
{"bytes20", common.Address{}, ""},
|
||||
{"address", [20]byte{}, ""},
|
||||
{"address", common.Address{}, ""},
|
||||
{"bytes32[]]", "", "invalid arg type in abi"},
|
||||
{"invalidType", "", "unsupported arg type: invalidType"},
|
||||
{"invalidSlice[]", "", "unsupported arg type: invalidSlice"},
|
||||
} {
|
||||
typ, err := NewType(test.typ)
|
||||
if err != nil && len(test.err) == 0 {
|
||||
t.Fatal("unexpected parse error:", err)
|
||||
} else if err != nil && len(test.err) != 0 {
|
||||
if err.Error() != test.err {
|
||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err = typeCheck(typ, reflect.ValueOf(test.input))
|
||||
if err != nil && len(test.err) == 0 {
|
||||
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if err == nil && len(test.err) != 0 {
|
||||
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// reads the integer based on its kind
|
||||
func readInteger(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:
|
||||
return int8(b[len(b)-1])
|
||||
case reflect.Int16:
|
||||
return int16(binary.BigEndian.Uint16(b[len(b)-2:]))
|
||||
case reflect.Int32:
|
||||
return int32(binary.BigEndian.Uint32(b[len(b)-4:]))
|
||||
case reflect.Int64:
|
||||
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
||||
default:
|
||||
return new(big.Int).SetBytes(b)
|
||||
}
|
||||
}
|
||||
|
||||
// reads a bool
|
||||
func readBool(word []byte) (bool, error) {
|
||||
for _, b := range word[:31] {
|
||||
if b != 0 {
|
||||
return false, errBadBool
|
||||
}
|
||||
}
|
||||
switch word[31] {
|
||||
case 0:
|
||||
return false, nil
|
||||
case 1:
|
||||
return true, nil
|
||||
default:
|
||||
return false, errBadBool
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
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")
|
||||
}
|
||||
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||
} else {
|
||||
copy(funcTy[:], word[0:24])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// through reflection, creates 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()
|
||||
|
||||
reflect.Copy(array, reflect.ValueOf(word[0:t.Size]))
|
||||
return array.Interface(), nil
|
||||
|
||||
}
|
||||
|
||||
func getFullElemSize(elem *Type) int {
|
||||
//all other should be counted as 32 (slices have pointers to respective elements)
|
||||
size := 32
|
||||
//arrays wrap it, each element being the same size
|
||||
for elem.T == ArrayTy {
|
||||
size *= elem.Size
|
||||
elem = elem.Elem
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
if start+32*size > len(output) {
|
||||
return nil, fmt.Errorf("abi: cannot marshal in to go array: offset %d would go over slice boundary (len=%d)", len(output), start+32*size)
|
||||
}
|
||||
|
||||
// this value will become our slice or our array, depending on the type
|
||||
var refSlice reflect.Value
|
||||
|
||||
if t.T == SliceTy {
|
||||
// declare our slice
|
||||
refSlice = reflect.MakeSlice(t.Type, size, size)
|
||||
} else if t.T == ArrayTy {
|
||||
// declare our array
|
||||
refSlice = reflect.New(t.Type).Elem()
|
||||
} else {
|
||||
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
||||
}
|
||||
|
||||
// Arrays have packed elements, resulting in longer unpack steps.
|
||||
// Slices have just 32 bytes per element (pointing to the contents).
|
||||
elemSize := 32
|
||||
if t.T == ArrayTy {
|
||||
elemSize = getFullElemSize(t.Elem)
|
||||
}
|
||||
|
||||
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
|
||||
|
||||
inter, err := toGoType(i, *t.Elem, output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// append the item to our reflect slice
|
||||
refSlice.Index(j).Set(reflect.ValueOf(inter))
|
||||
}
|
||||
|
||||
// return the interface
|
||||
return refSlice.Interface(), nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
}
|
||||
|
||||
var (
|
||||
returnOutput []byte
|
||||
begin, end int
|
||||
err error
|
||||
)
|
||||
|
||||
// if we require a length prefix, find the beginning word and size returned.
|
||||
if t.requiresLengthPrefix() {
|
||||
begin, end, err = lengthPrefixPointsTo(index, output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
returnOutput = output[index : index+32]
|
||||
}
|
||||
|
||||
switch t.T {
|
||||
case SliceTy:
|
||||
return forEachUnpack(t, output, begin, end)
|
||||
case ArrayTy:
|
||||
return forEachUnpack(t, output, index, t.Size)
|
||||
case StringTy: // variable arrays are written at the end of the return bytes
|
||||
return string(output[begin : begin+end]), nil
|
||||
case IntTy, UintTy:
|
||||
return readInteger(t.Kind, returnOutput), nil
|
||||
case BoolTy:
|
||||
return readBool(returnOutput)
|
||||
case AddressTy:
|
||||
return common.BytesToAddress(returnOutput), nil
|
||||
case HashTy:
|
||||
return common.BytesToHash(returnOutput), nil
|
||||
case BytesTy:
|
||||
return output[begin : begin+end], nil
|
||||
case FixedBytesTy:
|
||||
return readFixedBytes(t, returnOutput)
|
||||
case FunctionTy:
|
||||
return readFunctionType(t, returnOutput)
|
||||
default:
|
||||
return nil, fmt.Errorf("abi: unknown type %v", t.T)
|
||||
}
|
||||
}
|
||||
|
||||
// interprets a 32 byte slice as an offset and then determines which indice 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)
|
||||
outputLength := big.NewInt(int64(len(output)))
|
||||
|
||||
if bigOffsetEnd.Cmp(outputLength) > 0 {
|
||||
return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", bigOffsetEnd, outputLength)
|
||||
}
|
||||
|
||||
if bigOffsetEnd.BitLen() > 63 {
|
||||
return 0, 0, fmt.Errorf("abi offset larger than int64: %v", bigOffsetEnd)
|
||||
}
|
||||
|
||||
offsetEnd := int(bigOffsetEnd.Uint64())
|
||||
lengthBig := big.NewInt(0).SetBytes(output[offsetEnd-32 : offsetEnd])
|
||||
|
||||
totalSize := big.NewInt(0)
|
||||
totalSize.Add(totalSize, bigOffsetEnd)
|
||||
totalSize.Add(totalSize, lengthBig)
|
||||
if totalSize.BitLen() > 63 {
|
||||
return 0, 0, fmt.Errorf("abi length larger than int64: %v", totalSize)
|
||||
}
|
||||
|
||||
if totalSize.Cmp(outputLength) > 0 {
|
||||
return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %v require %v", outputLength, totalSize)
|
||||
}
|
||||
start = int(bigOffsetEnd.Uint64())
|
||||
length = int(lengthBig.Uint64())
|
||||
return
|
||||
}
|
||||
@@ -1,817 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type unpackTest struct {
|
||||
def string // ABI definition JSON
|
||||
enc string // evm return data
|
||||
want interface{} // the expected output
|
||||
err string // empty or error if expected
|
||||
}
|
||||
|
||||
func (test unpackTest) checkError(err error) error {
|
||||
if err != nil {
|
||||
if len(test.err) == 0 {
|
||||
return fmt.Errorf("expected no err but got: %v", err)
|
||||
} else if err.Error() != test.err {
|
||||
return fmt.Errorf("expected err: '%v' got err: %q", test.err, err)
|
||||
}
|
||||
} else if len(test.err) > 0 {
|
||||
return fmt.Errorf("expected err: %v but got none", test.err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var unpackTests = []unpackTest{
|
||||
{
|
||||
def: `[{ "type": "bool" }]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
def: `[{ "type": "bool" }]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
def: `[{ "type": "bool" }]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000001000000000001",
|
||||
want: false,
|
||||
err: "abi: improperly encoded boolean value",
|
||||
},
|
||||
{
|
||||
def: `[{ "type": "bool" }]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000003",
|
||||
want: false,
|
||||
err: "abi: improperly encoded boolean value",
|
||||
},
|
||||
{
|
||||
def: `[{"type": "uint32"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
want: uint32(1),
|
||||
},
|
||||
{
|
||||
def: `[{"type": "uint32"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
want: uint16(0),
|
||||
err: "abi: cannot unmarshal uint32 in to uint16",
|
||||
},
|
||||
{
|
||||
def: `[{"type": "uint17"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
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",
|
||||
want: int16(0),
|
||||
err: "abi: cannot unmarshal int32 in to int16",
|
||||
},
|
||||
{
|
||||
def: `[{"type": "int17"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
want: int16(0),
|
||||
err: "abi: cannot unmarshal *big.Int in to int16",
|
||||
},
|
||||
{
|
||||
def: `[{"type": "int17"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||
want: big.NewInt(1),
|
||||
},
|
||||
{
|
||||
def: `[{"type": "address"}]`,
|
||||
enc: "0000000000000000000000000100000000000000000000000000000000000000",
|
||||
want: common.Address{1},
|
||||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
{
|
||||
def: `[{"type": "bytes32"}]`,
|
||||
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000000000000000000000000000000000000000000",
|
||||
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},
|
||||
},
|
||||
{
|
||||
def: `[{"type": "function"}]`,
|
||||
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||
want: [24]byte{1},
|
||||
},
|
||||
// slices
|
||||
{
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: []uint8{1, 2},
|
||||
},
|
||||
{
|
||||
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: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000E0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: [][]uint8{{1, 2}, {1, 2}},
|
||||
},
|
||||
{
|
||||
def: `[{"type": "uint8[2][2]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: [2][2]uint8{{1, 2}, {1, 2}},
|
||||
},
|
||||
{
|
||||
def: `[{"type": "uint8[][2]"}]`,
|
||||
enc: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
|
||||
want: [2][]uint8{{1}, {1}},
|
||||
},
|
||||
{
|
||||
def: `[{"type": "uint8[2][]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: [][2]uint8{{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": "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","type":"int256"},{"name":"Int","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: multiple outputs mapping to the same struct field 'Int'",
|
||||
},
|
||||
{
|
||||
def: `[{"name":"int","type":"int256"},{"name":"_int","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: multiple outputs mapping to the same struct field 'Int'",
|
||||
},
|
||||
{
|
||||
def: `[{"name":"Int","type":"int256"},{"name":"_int","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: multiple outputs mapping to the same struct field 'Int'",
|
||||
},
|
||||
{
|
||||
def: `[{"name":"Int","type":"int256"},{"name":"_","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: purely underscored output cannot unpack to struct",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnpack(t *testing.T) {
|
||||
for i, test := range unpackTests {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
def := fmt.Sprintf(`[{ "name" : "method", "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.enc)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid hex: %s" + test.enc)
|
||||
}
|
||||
outptr := reflect.New(reflect.TypeOf(test.want))
|
||||
err = abi.Unpack(outptr.Interface(), "method", encb)
|
||||
if err := test.checkError(err); err != nil {
|
||||
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
|
||||
return
|
||||
}
|
||||
out := outptr.Elem().Interface()
|
||||
if !reflect.DeepEqual(test.want, out) {
|
||||
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type methodMultiOutput struct {
|
||||
Int *big.Int
|
||||
String string
|
||||
}
|
||||
|
||||
func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
|
||||
const definition = `[
|
||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||
var expected = methodMultiOutput{big.NewInt(1), "hello"}
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
require.NoError(err)
|
||||
// using buff to make the code readable
|
||||
buff := new(bytes.Buffer)
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||
buff.Write(common.RightPadBytes([]byte(expected.String), 32))
|
||||
return abi, buff.Bytes(), expected
|
||||
}
|
||||
|
||||
func TestMethodMultiReturn(t *testing.T) {
|
||||
type reversed struct {
|
||||
String string
|
||||
Int *big.Int
|
||||
}
|
||||
|
||||
abi, data, expected := methodMultiReturn(require.New(t))
|
||||
bigint := new(big.Int)
|
||||
var testCases = []struct {
|
||||
dest interface{}
|
||||
expected interface{}
|
||||
error string
|
||||
name string
|
||||
}{{
|
||||
&methodMultiOutput{},
|
||||
&expected,
|
||||
"",
|
||||
"Can unpack into structure",
|
||||
}, {
|
||||
&reversed{},
|
||||
&reversed{expected.String, expected.Int},
|
||||
"",
|
||||
"Can unpack into reversed structure",
|
||||
}, {
|
||||
&[]interface{}{&bigint, new(string)},
|
||||
&[]interface{}{&expected.Int, &expected.String},
|
||||
"",
|
||||
"Can unpack into a slice",
|
||||
}, {
|
||||
&[2]interface{}{&bigint, new(string)},
|
||||
&[2]interface{}{&expected.Int, &expected.String},
|
||||
"",
|
||||
"Can unpack into an array",
|
||||
}, {
|
||||
&[]interface{}{new(int), new(int)},
|
||||
&[]interface{}{&expected.Int, &expected.String},
|
||||
"abi: cannot unmarshal *big.Int in to int",
|
||||
"Can not unpack into a slice with wrong types",
|
||||
}, {
|
||||
&[]interface{}{new(int)},
|
||||
&[]interface{}{},
|
||||
"abi: insufficient number of elements in the list/array 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)
|
||||
if tc.error == "" {
|
||||
require.Nil(err, "Should be able to unpack method outputs.")
|
||||
require.Equal(tc.expected, tc.dest)
|
||||
} else {
|
||||
require.EqualError(err, tc.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiReturnWithArray(t *testing.T) {
|
||||
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buff := new(bytes.Buffer)
|
||||
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008"))
|
||||
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*ret1, ret1Exp) {
|
||||
t.Error("array result", *ret1, "!= Expected", ret1Exp)
|
||||
}
|
||||
if *ret2 != ret2Exp {
|
||||
t.Error("int result", *ret2, "!= Expected", ret2Exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
|
||||
// Similar to TestMultiReturnWithArray, but with a special case in mind:
|
||||
// values of nested static arrays count towards the size as well, and any element following
|
||||
// after such nested array argument should be read with the correct offset,
|
||||
// so that it does not read content from the previous array argument.
|
||||
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]`
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buff := new(bytes.Buffer)
|
||||
// construct the test array, each 3 char element is joined with 61 '0' chars,
|
||||
// to from the ((3 + 61) * 0.5) = 32 byte elements in the array.
|
||||
buff.Write(common.Hex2Bytes(strings.Join([]string{
|
||||
"", //empty, to apply the 61-char separator to the first element as well.
|
||||
"111", "112", "113", "121", "122", "123",
|
||||
"211", "212", "213", "221", "222", "223",
|
||||
"311", "312", "313", "321", "322", "323",
|
||||
"411", "412", "413", "421", "422", "423",
|
||||
}, "0000000000000000000000000000000000000000000000000000000000000")))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000009876"))
|
||||
|
||||
ret1, ret1Exp := new([4][2][3]uint64), [4][2][3]uint64{
|
||||
{{0x111, 0x112, 0x113}, {0x121, 0x122, 0x123}},
|
||||
{{0x211, 0x212, 0x213}, {0x221, 0x222, 0x223}},
|
||||
{{0x311, 0x312, 0x313}, {0x321, 0x322, 0x323}},
|
||||
{{0x411, 0x412, 0x413}, {0x421, 0x422, 0x423}},
|
||||
}
|
||||
ret2, ret2Exp := new(uint64), uint64(0x9876)
|
||||
if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*ret1, ret1Exp) {
|
||||
t.Error("array result", *ret1, "!= Expected", ret1Exp)
|
||||
}
|
||||
if *ret2 != ret2Exp {
|
||||
t.Error("int result", *ret2, "!= Expected", ret2Exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
||||
{ "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] },
|
||||
{ "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] },
|
||||
{ "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||
{ "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
|
||||
{ "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] },
|
||||
{ "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] },
|
||||
{ "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
|
||||
{ "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
// marshall mixed bytes (mixedBytes)
|
||||
p0, p0Exp := []byte{}, common.Hex2Bytes("01020000000000000000")
|
||||
p1, p1Exp := [32]byte{}, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff")
|
||||
mixedBytes := []interface{}{&p0, &p1}
|
||||
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000ddeeff"))
|
||||
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000a"))
|
||||
buff.Write(common.Hex2Bytes("0102000000000000000000000000000000000000000000000000000000000000"))
|
||||
|
||||
err = abi.Unpack(&mixedBytes, "mixedBytes", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if !bytes.Equal(p0, p0Exp) {
|
||||
t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0)
|
||||
}
|
||||
|
||||
if !bytes.Equal(p1[:], p1Exp) {
|
||||
t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1)
|
||||
}
|
||||
}
|
||||
|
||||
// marshal int
|
||||
var Int *big.Int
|
||||
err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if Int == nil || Int.Cmp(big.NewInt(1)) != 0 {
|
||||
t.Error("expected Int to be 1 got", Int)
|
||||
}
|
||||
|
||||
// marshal bool
|
||||
var Bool bool
|
||||
err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !Bool {
|
||||
t.Error("expected Bool to be true")
|
||||
}
|
||||
|
||||
// marshal dynamic bytes max length 32
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||
bytesOut := common.RightPadBytes([]byte("hello"), 32)
|
||||
buff.Write(bytesOut)
|
||||
|
||||
var Bytes []byte
|
||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(Bytes, bytesOut) {
|
||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||
}
|
||||
|
||||
// marshall dynamic bytes max length 64
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||
bytesOut = common.RightPadBytes([]byte("hello"), 64)
|
||||
buff.Write(bytesOut)
|
||||
|
||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(Bytes, bytesOut) {
|
||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||
}
|
||||
|
||||
// marshall dynamic bytes max length 64
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f"))
|
||||
bytesOut = common.RightPadBytes([]byte("hello"), 64)
|
||||
buff.Write(bytesOut)
|
||||
|
||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(Bytes, bytesOut[:len(bytesOut)-1]) {
|
||||
t.Errorf("expected %x got %x", bytesOut[:len(bytesOut)-1], Bytes)
|
||||
}
|
||||
|
||||
// marshal dynamic bytes output empty
|
||||
err = abi.Unpack(&Bytes, "bytes", nil)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
// marshal dynamic bytes length 5
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||
buff.Write(common.RightPadBytes([]byte("hello"), 32))
|
||||
|
||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(Bytes, []byte("hello")) {
|
||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||
}
|
||||
|
||||
// marshal dynamic bytes length 5
|
||||
buff.Reset()
|
||||
buff.Write(common.RightPadBytes([]byte("hello"), 32))
|
||||
|
||||
var hash common.Hash
|
||||
err = abi.Unpack(&hash, "fixed", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32))
|
||||
if hash != helloHash {
|
||||
t.Errorf("Expected %x to equal %x", hash, helloHash)
|
||||
}
|
||||
|
||||
// marshal error
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
err = abi.Unpack(&Bytes, "multi", make([]byte, 64))
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003"))
|
||||
// marshal int array
|
||||
var intArray [3]*big.Int
|
||||
err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
var testAgainstIntArray [3]*big.Int
|
||||
testAgainstIntArray[0] = big.NewInt(1)
|
||||
testAgainstIntArray[1] = big.NewInt(2)
|
||||
testAgainstIntArray[2] = big.NewInt(3)
|
||||
|
||||
for i, Int := range intArray {
|
||||
if Int.Cmp(testAgainstIntArray[i]) != 0 {
|
||||
t.Errorf("expected %v, got %v", testAgainstIntArray[i], Int)
|
||||
}
|
||||
}
|
||||
// marshal address slice
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"))
|
||||
|
||||
var outAddr []common.Address
|
||||
err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal("didn't expect error:", err)
|
||||
}
|
||||
|
||||
if len(outAddr) != 1 {
|
||||
t.Fatal("expected 1 item, got", len(outAddr))
|
||||
}
|
||||
|
||||
if outAddr[0] != (common.Address{1}) {
|
||||
t.Errorf("expected %x, got %x", common.Address{1}, outAddr[0])
|
||||
}
|
||||
|
||||
// marshal multiple address slice
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // offset
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // offset
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // size
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000200000000000000000000000000000000000000"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000300000000000000000000000000000000000000"))
|
||||
|
||||
var outAddrStruct struct {
|
||||
A []common.Address
|
||||
B []common.Address
|
||||
}
|
||||
err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes())
|
||||
if err != nil {
|
||||
t.Fatal("didn't expect error:", err)
|
||||
}
|
||||
|
||||
if len(outAddrStruct.A) != 1 {
|
||||
t.Fatal("expected 1 item, got", len(outAddrStruct.A))
|
||||
}
|
||||
|
||||
if outAddrStruct.A[0] != (common.Address{1}) {
|
||||
t.Errorf("expected %x, got %x", common.Address{1}, outAddrStruct.A[0])
|
||||
}
|
||||
|
||||
if len(outAddrStruct.B) != 2 {
|
||||
t.Fatal("expected 1 item, got", len(outAddrStruct.B))
|
||||
}
|
||||
|
||||
if outAddrStruct.B[0] != (common.Address{2}) {
|
||||
t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0])
|
||||
}
|
||||
if outAddrStruct.B[1] != (common.Address{3}) {
|
||||
t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1])
|
||||
}
|
||||
|
||||
// marshal invalid address slice
|
||||
buff.Reset()
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100"))
|
||||
|
||||
err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes())
|
||||
if err == nil {
|
||||
t.Fatal("expected error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOOMMaliciousInput(t *testing.T) {
|
||||
oomTests := []unpackTest{
|
||||
{
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||
"0000000000000000000000000000000000000000000000000000000000000003" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
{ // Length larger than 64 bits
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||
"00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000002" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
{ // Offset very large (over 64 bits)
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "00ffffffffffffffffffffffffffffffffffffffffffffff0000000000000020" + // offset
|
||||
"0000000000000000000000000000000000000000000000000000000000000002" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
{ // Offset very large (below 64 bits)
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000007ffffffffff00020" + // offset
|
||||
"0000000000000000000000000000000000000000000000000000000000000002" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
{ // Offset negative (as 64 bit)
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "000000000000000000000000000000000000000000000000f000000000000020" + // offset
|
||||
"0000000000000000000000000000000000000000000000000000000000000002" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
|
||||
{ // Negative length
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||
"000000000000000000000000000000000000000000000000f000000000000002" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
{ // Very large length
|
||||
def: `[{"type": "uint8[]"}]`,
|
||||
enc: "0000000000000000000000000000000000000000000000000000000000000020" + // offset
|
||||
"0000000000000000000000000000000000000000000000007fffffffff000002" + // num elems
|
||||
"0000000000000000000000000000000000000000000000000000000000000001" + // elem 1
|
||||
"0000000000000000000000000000000000000000000000000000000000000002", // elem 2
|
||||
},
|
||||
}
|
||||
for i, test := range oomTests {
|
||||
def := fmt.Sprintf(`[{ "name" : "method", "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.enc)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid hex: %s" + test.enc)
|
||||
}
|
||||
_, err = abi.Methods["method"].Outputs.UnpackValues(encb)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error on malicious input, test %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package accounts implements high level Ethereum account management.
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// Account represents an Ethereum account located at a specific location defined
|
||||
// by the optional URL field.
|
||||
type Account struct {
|
||||
Address common.Address `json:"address"` // Ethereum account address derived from the key
|
||||
URL URL `json:"url"` // Optional resource locator within a backend
|
||||
}
|
||||
|
||||
// Wallet represents a software or hardware wallet that might contain one or more
|
||||
// accounts (derived from the same seed).
|
||||
type Wallet interface {
|
||||
// URL retrieves the canonical path under which this wallet is reachable. It is
|
||||
// user by upper layers to define a sorting order over all wallets from multiple
|
||||
// backends.
|
||||
URL() URL
|
||||
|
||||
// Status returns a textual status to aid the user in the current state of the
|
||||
// wallet. It also returns an error indicating any failure the wallet might have
|
||||
// encountered.
|
||||
Status() (string, error)
|
||||
|
||||
// Open initializes access to a wallet instance. It is not meant to unlock or
|
||||
// decrypt account keys, rather simply to establish a connection to hardware
|
||||
// wallets and/or to access derivation seeds.
|
||||
//
|
||||
// The passphrase parameter may or may not be used by the implementation of a
|
||||
// particular wallet instance. The reason there is no passwordless open method
|
||||
// is to strive towards a uniform wallet handling, oblivious to the different
|
||||
// backend providers.
|
||||
//
|
||||
// Please note, if you open a wallet, you must close it to release any allocated
|
||||
// resources (especially important when working with hardware wallets).
|
||||
Open(passphrase string) error
|
||||
|
||||
// Close releases any resources held by an open wallet instance.
|
||||
Close() error
|
||||
|
||||
// Accounts retrieves the list of signing accounts the wallet is currently aware
|
||||
// of. For hierarchical deterministic wallets, the list will not be exhaustive,
|
||||
// rather only contain the accounts explicitly pinned during account derivation.
|
||||
Accounts() []Account
|
||||
|
||||
// Contains returns whether an account is part of this particular wallet or not.
|
||||
Contains(account Account) bool
|
||||
|
||||
// Derive attempts to explicitly derive a hierarchical deterministic account at
|
||||
// the specified derivation path. If requested, the derived account will be added
|
||||
// to the wallet's tracked account list.
|
||||
Derive(path DerivationPath, pin bool) (Account, error)
|
||||
|
||||
// SelfDerive sets a base account derivation path from which the wallet attempts
|
||||
// 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
|
||||
// opposed to decending into a child path to allow discovering accounts starting
|
||||
// from non zero components.
|
||||
//
|
||||
// You can disable automatic account discovery by calling SelfDerive with a nil
|
||||
// chain state reader.
|
||||
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
|
||||
|
||||
// SignHash requests the wallet to sign the given hash.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
//
|
||||
// If the wallet requires additional authentication to sign the request (e.g.
|
||||
// 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 account in a keystore).
|
||||
SignHash(account Account, hash []byte) ([]byte, error)
|
||||
|
||||
// SignTx requests the wallet to sign the given transaction.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
//
|
||||
// If the wallet requires additional authentication to sign the request (e.g.
|
||||
// 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 SignTxWithPassphrase, or by other means (e.g. unlock
|
||||
// the account in a keystore).
|
||||
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||
|
||||
// SignHashWithPassphrase requests the wallet to sign the given hash with the
|
||||
// given passphrase as extra authentication information.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
SignHashWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
|
||||
|
||||
// SignTxWithPassphrase requests the wallet to sign the given transaction, with the
|
||||
// given passphrase as extra authentication information.
|
||||
//
|
||||
// It looks up the account specified either solely via its address contained within,
|
||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||
}
|
||||
|
||||
// Backend is a "wallet provider" that may contain a batch of accounts they can
|
||||
// sign transactions with and upon request, do so.
|
||||
type Backend interface {
|
||||
// Wallets retrieves the list of wallets the backend is currently aware of.
|
||||
//
|
||||
// The returned wallets are not opened by default. For software HD wallets this
|
||||
// means that no base seeds are decrypted, and for hardware wallets that no actual
|
||||
// connection is established.
|
||||
//
|
||||
// The resulting wallet list will be sorted alphabetically based on its internal
|
||||
// URL assigned by the backend. Since wallets (especially hardware) may come and
|
||||
// go, the same wallet might appear at a different positions in the list during
|
||||
// subsequent retrievals.
|
||||
Wallets() []Wallet
|
||||
|
||||
// Subscribe creates an async subscription to receive notifications when the
|
||||
// backend detects the arrival or departure of a wallet.
|
||||
Subscribe(sink chan<- WalletEvent) event.Subscription
|
||||
}
|
||||
|
||||
// WalletEventType represents the different event types that can be fired by
|
||||
// the wallet subscription subsystem.
|
||||
type WalletEventType int
|
||||
|
||||
const (
|
||||
// WalletArrived is fired when a new wallet is detected either via USB or via
|
||||
// a filesystem event in the keystore.
|
||||
WalletArrived WalletEventType = iota
|
||||
|
||||
// WalletOpened is fired when a wallet is successfully opened with the purpose
|
||||
// of starting any background processes such as automatic key derivation.
|
||||
WalletOpened
|
||||
|
||||
// WalletDropped
|
||||
WalletDropped
|
||||
)
|
||||
|
||||
// WalletEvent is an event fired by an account backend when a wallet arrival or
|
||||
// departure is detected.
|
||||
type WalletEvent struct {
|
||||
Wallet Wallet // Wallet instance arrived or departed
|
||||
Kind WalletEventType // Event type that happened in the system
|
||||
}
|
||||
@@ -1,68 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrUnknownAccount is returned for any requested operation for which no backend
|
||||
// provides the specified account.
|
||||
var ErrUnknownAccount = errors.New("unknown account")
|
||||
|
||||
// ErrUnknownWallet is returned for any requested operation for which no backend
|
||||
// provides the specified wallet.
|
||||
var ErrUnknownWallet = errors.New("unknown wallet")
|
||||
|
||||
// ErrNotSupported is returned when an operation is requested from an account
|
||||
// backend that it does not support.
|
||||
var ErrNotSupported = errors.New("not supported")
|
||||
|
||||
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
|
||||
// passphrase.
|
||||
var ErrInvalidPassphrase = errors.New("invalid passphrase")
|
||||
|
||||
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
||||
// second time.
|
||||
var ErrWalletAlreadyOpen = errors.New("wallet already open")
|
||||
|
||||
// ErrWalletClosed is returned if a wallet is attempted to be opened the
|
||||
// secodn time.
|
||||
var ErrWalletClosed = errors.New("wallet closed")
|
||||
|
||||
// AuthNeededError is returned by backends for signing requests where the user
|
||||
// is required to provide further authentication before signing can succeed.
|
||||
//
|
||||
// This usually means either that a password needs to be supplied, or perhaps a
|
||||
// one time PIN code displayed by some hardware device.
|
||||
type AuthNeededError struct {
|
||||
Needed string // Extra authentication the user needs to provide
|
||||
}
|
||||
|
||||
// NewAuthNeededError creates a new authentication error with the extra details
|
||||
// about the needed fields set.
|
||||
func NewAuthNeededError(needed string) error {
|
||||
return &AuthNeededError{
|
||||
Needed: needed,
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the standard error interface.
|
||||
func (err *AuthNeededError) Error() string {
|
||||
return fmt.Sprintf("authentication needed: %s", err.Needed)
|
||||
}
|
||||
135
accounts/hd.go
135
accounts/hd.go
@@ -1,135 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultRootDerivationPath is the root path to which custom derivation endpoints
|
||||
// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||
// at m/44'/60'/0'/1, etc.
|
||||
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||
|
||||
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||
// at m/44'/60'/0'/1, etc.
|
||||
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
||||
|
||||
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||
// at m/44'/60'/0'/1, etc.
|
||||
var DefaultLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||
|
||||
// DerivationPath represents the computer friendly version of a hierarchical
|
||||
// deterministic wallet account derivaion path.
|
||||
//
|
||||
// The BIP-32 spec https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
|
||||
// defines derivation paths to be of the form:
|
||||
//
|
||||
// m / purpose' / coin_type' / account' / change / address_index
|
||||
//
|
||||
// The BIP-44 spec https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
|
||||
// defines that the `purpose` be 44' (or 0x8000002C) for crypto currencies, and
|
||||
// SLIP-44 https://github.com/satoshilabs/slips/blob/master/slip-0044.md assigns
|
||||
// the `coin_type` 60' (or 0x8000003C) to Ethereum.
|
||||
//
|
||||
// The root path for Ethereum is m/44'/60'/0'/0 according to the specification
|
||||
// from https://github.com/ethereum/EIPs/issues/84, albeit it's not set in stone
|
||||
// yet whether accounts should increment the last component or the children of
|
||||
// that. We will go with the simpler approach of incrementing the last component.
|
||||
type DerivationPath []uint32
|
||||
|
||||
// ParseDerivationPath converts a user specified derivation path string to the
|
||||
// internal binary representation.
|
||||
//
|
||||
// Full derivation paths need to start with the `m/` prefix, relative derivation
|
||||
// paths (which will get appended to the default root path) must not have prefixes
|
||||
// in front of the first element. Whitespace is ignored.
|
||||
func ParseDerivationPath(path string) (DerivationPath, error) {
|
||||
var result DerivationPath
|
||||
|
||||
// Handle absolute or relative paths
|
||||
components := strings.Split(path, "/")
|
||||
switch {
|
||||
case len(components) == 0:
|
||||
return nil, errors.New("empty derivation path")
|
||||
|
||||
case strings.TrimSpace(components[0]) == "":
|
||||
return nil, errors.New("ambiguous path: use 'm/' prefix for absolute paths, or no leading '/' for relative ones")
|
||||
|
||||
case strings.TrimSpace(components[0]) == "m":
|
||||
components = components[1:]
|
||||
|
||||
default:
|
||||
result = append(result, DefaultRootDerivationPath...)
|
||||
}
|
||||
// All remaining components are relative, append one by one
|
||||
if len(components) == 0 {
|
||||
return nil, errors.New("empty derivation path") // Empty relative paths
|
||||
}
|
||||
for _, component := range components {
|
||||
// Ignore any user added whitespace
|
||||
component = strings.TrimSpace(component)
|
||||
var value uint32
|
||||
|
||||
// Handle hardened paths
|
||||
if strings.HasSuffix(component, "'") {
|
||||
value = 0x80000000
|
||||
component = strings.TrimSpace(strings.TrimSuffix(component, "'"))
|
||||
}
|
||||
// Handle the non hardened component
|
||||
bigval, ok := new(big.Int).SetString(component, 0)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid component: %s", component)
|
||||
}
|
||||
max := math.MaxUint32 - value
|
||||
if bigval.Sign() < 0 || bigval.Cmp(big.NewInt(int64(max))) > 0 {
|
||||
if value == 0 {
|
||||
return nil, fmt.Errorf("component %v out of allowed range [0, %d]", bigval, max)
|
||||
}
|
||||
return nil, fmt.Errorf("component %v out of allowed hardened range [0, %d]", bigval, max)
|
||||
}
|
||||
value += uint32(bigval.Uint64())
|
||||
|
||||
// Append and repeat
|
||||
result = append(result, value)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// String implements the stringer interface, converting a binary derivation path
|
||||
// to its canonical representation.
|
||||
func (path DerivationPath) String() string {
|
||||
result := "m"
|
||||
for _, component := range path {
|
||||
var hardened bool
|
||||
if component >= 0x80000000 {
|
||||
component -= 0x80000000
|
||||
hardened = true
|
||||
}
|
||||
result = fmt.Sprintf("%s/%d", result, component)
|
||||
if hardened {
|
||||
result += "'"
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1,79 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
||||
// representation.
|
||||
func TestHDPathParsing(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
output DerivationPath
|
||||
}{
|
||||
// Plain absolute derivation paths
|
||||
{"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||
{"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
|
||||
{"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||
{"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
|
||||
{"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||
|
||||
// Plain relative derivation paths
|
||||
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
|
||||
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
|
||||
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
|
||||
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||
|
||||
// Hexadecimal absolute derivation paths
|
||||
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||
{"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
|
||||
{"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||
{"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
|
||||
{"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||
|
||||
// Hexadecimal relative derivation paths
|
||||
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
|
||||
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
|
||||
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
|
||||
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||
|
||||
// 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
|
||||
{"", nil}, // Empty relative derivation path
|
||||
{"m", nil}, // Empty absolute derivation path
|
||||
{"m/", nil}, // Missing last derivation component
|
||||
{"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error
|
||||
{"m/2147483648'", nil}, // Overflows 32 bit integer
|
||||
{"m/-1'", nil}, // Cannot contain negative number
|
||||
}
|
||||
for i, tt := range tests {
|
||||
if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) {
|
||||
t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output)
|
||||
} else if path == nil && err == nil {
|
||||
t.Errorf("test %d: nil path and error: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,298 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
||||
// not support change notifications. It also applies if the keystore directory does not
|
||||
// exist yet, the code will attempt to create a watcher at most this often.
|
||||
const minReloadInterval = 2 * time.Second
|
||||
|
||||
type accountsByURL []accounts.Account
|
||||
|
||||
func (s accountsByURL) Len() int { return len(s) }
|
||||
func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 }
|
||||
func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// AmbiguousAddrError is returned when attempting to unlock
|
||||
// an address for which more than one file exists.
|
||||
type AmbiguousAddrError struct {
|
||||
Addr common.Address
|
||||
Matches []accounts.Account
|
||||
}
|
||||
|
||||
func (err *AmbiguousAddrError) Error() string {
|
||||
files := ""
|
||||
for i, a := range err.Matches {
|
||||
files += a.URL.Path
|
||||
if i < len(err.Matches)-1 {
|
||||
files += ", "
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("multiple keys match address (%s)", files)
|
||||
}
|
||||
|
||||
// accountCache is a live index of all accounts in the keystore.
|
||||
type accountCache struct {
|
||||
keydir string
|
||||
watcher *watcher
|
||||
mu sync.Mutex
|
||||
all accountsByURL
|
||||
byAddr map[common.Address][]accounts.Account
|
||||
throttle *time.Timer
|
||||
notify chan struct{}
|
||||
fileC fileCache
|
||||
}
|
||||
|
||||
func newAccountCache(keydir string) (*accountCache, chan struct{}) {
|
||||
ac := &accountCache{
|
||||
keydir: keydir,
|
||||
byAddr: make(map[common.Address][]accounts.Account),
|
||||
notify: make(chan struct{}, 1),
|
||||
fileC: fileCache{all: set.NewNonTS()},
|
||||
}
|
||||
ac.watcher = newWatcher(ac)
|
||||
return ac, ac.notify
|
||||
}
|
||||
|
||||
func (ac *accountCache) accounts() []accounts.Account {
|
||||
ac.maybeReload()
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
cpy := make([]accounts.Account, len(ac.all))
|
||||
copy(cpy, ac.all)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (ac *accountCache) hasAddress(addr common.Address) bool {
|
||||
ac.maybeReload()
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
return len(ac.byAddr[addr]) > 0
|
||||
}
|
||||
|
||||
func (ac *accountCache) add(newAccount accounts.Account) {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
|
||||
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Cmp(newAccount.URL) >= 0 })
|
||||
if i < len(ac.all) && ac.all[i] == newAccount {
|
||||
return
|
||||
}
|
||||
// newAccount is not in the cache.
|
||||
ac.all = append(ac.all, accounts.Account{})
|
||||
copy(ac.all[i+1:], ac.all[i:])
|
||||
ac.all[i] = newAccount
|
||||
ac.byAddr[newAccount.Address] = append(ac.byAddr[newAccount.Address], newAccount)
|
||||
}
|
||||
|
||||
// note: removed needs to be unique here (i.e. both File and Address must be set).
|
||||
func (ac *accountCache) delete(removed accounts.Account) {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
|
||||
ac.all = removeAccount(ac.all, removed)
|
||||
if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
|
||||
delete(ac.byAddr, removed.Address)
|
||||
} else {
|
||||
ac.byAddr[removed.Address] = ba
|
||||
}
|
||||
}
|
||||
|
||||
// deleteByFile removes an account referenced by the given path.
|
||||
func (ac *accountCache) deleteByFile(path string) {
|
||||
ac.mu.Lock()
|
||||
defer ac.mu.Unlock()
|
||||
i := sort.Search(len(ac.all), func(i int) bool { return ac.all[i].URL.Path >= path })
|
||||
|
||||
if i < len(ac.all) && ac.all[i].URL.Path == path {
|
||||
removed := ac.all[i]
|
||||
ac.all = append(ac.all[:i], ac.all[i+1:]...)
|
||||
if ba := removeAccount(ac.byAddr[removed.Address], removed); len(ba) == 0 {
|
||||
delete(ac.byAddr, removed.Address)
|
||||
} else {
|
||||
ac.byAddr[removed.Address] = ba
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeAccount(slice []accounts.Account, elem accounts.Account) []accounts.Account {
|
||||
for i := range slice {
|
||||
if slice[i] == elem {
|
||||
return append(slice[:i], slice[i+1:]...)
|
||||
}
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// find returns the cached account for address if there is a unique match.
|
||||
// The exact matching rules are explained by the documentation of accounts.Account.
|
||||
// Callers must hold ac.mu.
|
||||
func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) {
|
||||
// Limit search to address candidates if possible.
|
||||
matches := ac.all
|
||||
if (a.Address != common.Address{}) {
|
||||
matches = ac.byAddr[a.Address]
|
||||
}
|
||||
if a.URL.Path != "" {
|
||||
// If only the basename is specified, complete the path.
|
||||
if !strings.ContainsRune(a.URL.Path, filepath.Separator) {
|
||||
a.URL.Path = filepath.Join(ac.keydir, a.URL.Path)
|
||||
}
|
||||
for i := range matches {
|
||||
if matches[i].URL == a.URL {
|
||||
return matches[i], nil
|
||||
}
|
||||
}
|
||||
if (a.Address == common.Address{}) {
|
||||
return accounts.Account{}, ErrNoMatch
|
||||
}
|
||||
}
|
||||
switch len(matches) {
|
||||
case 1:
|
||||
return matches[0], nil
|
||||
case 0:
|
||||
return accounts.Account{}, ErrNoMatch
|
||||
default:
|
||||
err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))}
|
||||
copy(err.Matches, matches)
|
||||
sort.Sort(accountsByURL(err.Matches))
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *accountCache) maybeReload() {
|
||||
ac.mu.Lock()
|
||||
|
||||
if ac.watcher.running {
|
||||
ac.mu.Unlock()
|
||||
return // A watcher is running and will keep the cache up-to-date.
|
||||
}
|
||||
if ac.throttle == nil {
|
||||
ac.throttle = time.NewTimer(0)
|
||||
} else {
|
||||
select {
|
||||
case <-ac.throttle.C:
|
||||
default:
|
||||
ac.mu.Unlock()
|
||||
return // The cache was reloaded recently.
|
||||
}
|
||||
}
|
||||
// No watcher running, start it.
|
||||
ac.watcher.start()
|
||||
ac.throttle.Reset(minReloadInterval)
|
||||
ac.mu.Unlock()
|
||||
ac.scanAccounts()
|
||||
}
|
||||
|
||||
func (ac *accountCache) close() {
|
||||
ac.mu.Lock()
|
||||
ac.watcher.close()
|
||||
if ac.throttle != nil {
|
||||
ac.throttle.Stop()
|
||||
}
|
||||
if ac.notify != nil {
|
||||
close(ac.notify)
|
||||
ac.notify = nil
|
||||
}
|
||||
ac.mu.Unlock()
|
||||
}
|
||||
|
||||
// scanAccounts checks if any changes have occurred on the filesystem, and
|
||||
// updates the account cache accordingly
|
||||
func (ac *accountCache) scanAccounts() error {
|
||||
// Scan the entire folder metadata for file changes
|
||||
creates, deletes, updates, err := ac.fileC.scan(ac.keydir)
|
||||
if err != nil {
|
||||
log.Debug("Failed to reload keystore contents", "err", err)
|
||||
return err
|
||||
}
|
||||
if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
|
||||
return nil
|
||||
}
|
||||
// Create a helper method to scan the contents of the key files
|
||||
var (
|
||||
buf = new(bufio.Reader)
|
||||
key struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
)
|
||||
readAccount := func(path string) *accounts.Account {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Trace("Failed to open keystore file", "path", path, "err", err)
|
||||
return nil
|
||||
}
|
||||
defer fd.Close()
|
||||
buf.Reset(fd)
|
||||
// Parse the address.
|
||||
key.Address = ""
|
||||
err = json.NewDecoder(buf).Decode(&key)
|
||||
addr := common.HexToAddress(key.Address)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Debug("Failed to decode keystore key", "path", path, "err", err)
|
||||
case (addr == common.Address{}):
|
||||
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
|
||||
default:
|
||||
return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Process all the file diffs
|
||||
start := time.Now()
|
||||
|
||||
for _, p := range creates.List() {
|
||||
if a := readAccount(p.(string)); a != nil {
|
||||
ac.add(*a)
|
||||
}
|
||||
}
|
||||
for _, p := range deletes.List() {
|
||||
ac.deleteByFile(p.(string))
|
||||
}
|
||||
for _, p := range updates.List() {
|
||||
path := p.(string)
|
||||
ac.deleteByFile(path)
|
||||
if a := readAccount(path); a != nil {
|
||||
ac.add(*a)
|
||||
}
|
||||
}
|
||||
end := time.Now()
|
||||
|
||||
select {
|
||||
case ac.notify <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
log.Trace("Handled keystore changes", "time", end.Sub(start))
|
||||
return nil
|
||||
}
|
||||
@@ -1,406 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/cp"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var (
|
||||
cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
|
||||
cachetestAccounts = []accounts.Account{
|
||||
{
|
||||
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestWatchNewFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dir, ks := tmpKeyStore(t, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Ensure the watcher is started before adding any files.
|
||||
ks.Accounts()
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Move in the files.
|
||||
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
|
||||
for i := range cachetestAccounts {
|
||||
wantAccounts[i] = accounts.Account{
|
||||
Address: cachetestAccounts[i].Address,
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
|
||||
}
|
||||
if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ks should see the accounts.
|
||||
var list []accounts.Account
|
||||
for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
|
||||
list = ks.Accounts()
|
||||
if reflect.DeepEqual(list, wantAccounts) {
|
||||
// ks should have also received change notifications
|
||||
select {
|
||||
case <-ks.changes:
|
||||
default:
|
||||
t.Fatalf("wasn't notified of new accounts")
|
||||
}
|
||||
return
|
||||
}
|
||||
time.Sleep(d)
|
||||
}
|
||||
t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
|
||||
}
|
||||
|
||||
func TestWatchNoDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create ks but not the directory that it watches.
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||
|
||||
list := ks.Accounts()
|
||||
if len(list) > 0 {
|
||||
t.Error("initial account list not empty:", list)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Create the directory and copy a key file into it.
|
||||
os.MkdirAll(dir, 0700)
|
||||
defer os.RemoveAll(dir)
|
||||
file := filepath.Join(dir, "aaa")
|
||||
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// ks should see the account.
|
||||
wantAccounts := []accounts.Account{cachetestAccounts[0]}
|
||||
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
|
||||
list = ks.Accounts()
|
||||
if reflect.DeepEqual(list, wantAccounts) {
|
||||
// ks should have also received change notifications
|
||||
select {
|
||||
case <-ks.changes:
|
||||
default:
|
||||
t.Fatalf("wasn't notified of new accounts")
|
||||
}
|
||||
return
|
||||
}
|
||||
time.Sleep(d)
|
||||
}
|
||||
t.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||
}
|
||||
|
||||
func TestCacheInitialReload(t *testing.T) {
|
||||
cache, _ := newAccountCache(cachetestDir)
|
||||
accounts := cache.accounts()
|
||||
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
||||
t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheAddDeleteOrder(t *testing.T) {
|
||||
cache, _ := newAccountCache("testdata/no-such-dir")
|
||||
cache.watcher.running = true // prevent unexpected reloads
|
||||
|
||||
accs := []accounts.Account{
|
||||
{
|
||||
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
|
||||
},
|
||||
}
|
||||
for _, a := range accs {
|
||||
cache.add(a)
|
||||
}
|
||||
// Add some of them twice to check that they don't get reinserted.
|
||||
cache.add(accs[0])
|
||||
cache.add(accs[2])
|
||||
|
||||
// Check that the account list is sorted by filename.
|
||||
wantAccounts := make([]accounts.Account, len(accs))
|
||||
copy(wantAccounts, accs)
|
||||
sort.Sort(accountsByURL(wantAccounts))
|
||||
list := cache.accounts()
|
||||
if !reflect.DeepEqual(list, wantAccounts) {
|
||||
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
||||
}
|
||||
for _, a := range accs {
|
||||
if !cache.hasAddress(a.Address) {
|
||||
t.Errorf("expected hasAccount(%x) to return true", a.Address)
|
||||
}
|
||||
}
|
||||
if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
|
||||
t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
|
||||
}
|
||||
|
||||
// Delete a few keys from the cache.
|
||||
for i := 0; i < len(accs); i += 2 {
|
||||
cache.delete(wantAccounts[i])
|
||||
}
|
||||
cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
|
||||
|
||||
// Check content again after deletion.
|
||||
wantAccountsAfterDelete := []accounts.Account{
|
||||
wantAccounts[1],
|
||||
wantAccounts[3],
|
||||
wantAccounts[5],
|
||||
}
|
||||
list = cache.accounts()
|
||||
if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
|
||||
t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
|
||||
}
|
||||
for _, a := range wantAccountsAfterDelete {
|
||||
if !cache.hasAddress(a.Address) {
|
||||
t.Errorf("expected hasAccount(%x) to return true", a.Address)
|
||||
}
|
||||
}
|
||||
if cache.hasAddress(wantAccounts[0].Address) {
|
||||
t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCacheFind(t *testing.T) {
|
||||
dir := filepath.Join("testdata", "dir")
|
||||
cache, _ := newAccountCache(dir)
|
||||
cache.watcher.running = true // prevent unexpected reloads
|
||||
|
||||
accs := []accounts.Account{
|
||||
{
|
||||
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
|
||||
},
|
||||
{
|
||||
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
|
||||
},
|
||||
}
|
||||
for _, a := range accs {
|
||||
cache.add(a)
|
||||
}
|
||||
|
||||
nomatchAccount := accounts.Account{
|
||||
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
||||
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
|
||||
}
|
||||
tests := []struct {
|
||||
Query accounts.Account
|
||||
WantResult accounts.Account
|
||||
WantError error
|
||||
}{
|
||||
// by address
|
||||
{Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
|
||||
// by file
|
||||
{Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
|
||||
// by basename
|
||||
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
|
||||
// by file and address
|
||||
{Query: accs[0], WantResult: accs[0]},
|
||||
// ambiguous address, tie resolved by file
|
||||
{Query: accs[2], WantResult: accs[2]},
|
||||
// ambiguous address error
|
||||
{
|
||||
Query: accounts.Account{Address: accs[2].Address},
|
||||
WantError: &AmbiguousAddrError{
|
||||
Addr: accs[2].Address,
|
||||
Matches: []accounts.Account{accs[2], accs[3]},
|
||||
},
|
||||
},
|
||||
// no match error
|
||||
{Query: nomatchAccount, WantError: ErrNoMatch},
|
||||
{Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
|
||||
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
|
||||
{Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
|
||||
}
|
||||
for i, test := range tests {
|
||||
a, err := cache.find(test.Query)
|
||||
if !reflect.DeepEqual(err, test.WantError) {
|
||||
t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
|
||||
continue
|
||||
}
|
||||
if a != test.WantResult {
|
||||
t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||
var list []accounts.Account
|
||||
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
|
||||
list = ks.Accounts()
|
||||
if reflect.DeepEqual(list, wantAccounts) {
|
||||
// ks should have also received change notifications
|
||||
select {
|
||||
case <-ks.changes:
|
||||
default:
|
||||
return fmt.Errorf("wasn't notified of new accounts")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
time.Sleep(d)
|
||||
}
|
||||
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||
}
|
||||
|
||||
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
|
||||
// is noticed by the watcher, and the account cache is updated accordingly
|
||||
func TestUpdatedKeyfileContents(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a temporary kesytore to test with
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
||||
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||
|
||||
list := ks.Accounts()
|
||||
if len(list) > 0 {
|
||||
t.Error("initial account list not empty:", list)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Create the directory and copy a key file into it.
|
||||
os.MkdirAll(dir, 0700)
|
||||
defer os.RemoveAll(dir)
|
||||
file := filepath.Join(dir, "aaa")
|
||||
|
||||
// Place one of our testfiles in there
|
||||
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// ks should see the account.
|
||||
wantAccounts := []accounts.Account{cachetestAccounts[0]}
|
||||
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Now replace file contents
|
||||
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
wantAccounts = []accounts.Account{cachetestAccounts[1]}
|
||||
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||
t.Errorf("First replacement failed")
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Now replace file contents again
|
||||
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
wantAccounts = []accounts.Account{cachetestAccounts[2]}
|
||||
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||
t.Errorf("Second replacement failed")
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// needed so that modTime of `file` is different to its current value after ioutil.WriteFile
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Now replace file contents with crap
|
||||
if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
|
||||
t.Errorf("Emptying account file failed")
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
|
||||
func forceCopyFile(dst, src string) error {
|
||||
data, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(dst, data, 0644)
|
||||
}
|
||||
@@ -1,102 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
set "gopkg.in/fatih/set.v0"
|
||||
)
|
||||
|
||||
// fileCache is a cache of files seen during scan of keystore.
|
||||
type fileCache struct {
|
||||
all *set.SetNonTS // Set of all files from the keystore folder
|
||||
lastMod time.Time // Last time instance when a file was modified
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// scan performs a new scan on the given directory, compares against the already
|
||||
// cached filenames, and returns file sets: creates, deletes, updates.
|
||||
func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
|
||||
t0 := time.Now()
|
||||
|
||||
// List all the failes from the keystore folder
|
||||
files, err := ioutil.ReadDir(keyDir)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
t1 := time.Now()
|
||||
|
||||
fc.mu.Lock()
|
||||
defer fc.mu.Unlock()
|
||||
|
||||
// Iterate all the files and gather their metadata
|
||||
all := set.NewNonTS()
|
||||
mods := set.NewNonTS()
|
||||
|
||||
var newLastMod time.Time
|
||||
for _, fi := range files {
|
||||
// Skip any non-key files from the folder
|
||||
path := filepath.Join(keyDir, fi.Name())
|
||||
if skipKeyFile(fi) {
|
||||
log.Trace("Ignoring file on account scan", "path", path)
|
||||
continue
|
||||
}
|
||||
// Gather the set of all and fresly modified files
|
||||
all.Add(path)
|
||||
|
||||
modified := fi.ModTime()
|
||||
if modified.After(fc.lastMod) {
|
||||
mods.Add(path)
|
||||
}
|
||||
if modified.After(newLastMod) {
|
||||
newLastMod = modified
|
||||
}
|
||||
}
|
||||
t2 := time.Now()
|
||||
|
||||
// Update the tracked files and return the three sets
|
||||
deletes := set.Difference(fc.all, all) // Deletes = previous - current
|
||||
creates := set.Difference(all, fc.all) // Creates = current - previous
|
||||
updates := set.Difference(mods, creates) // Updates = modified - creates
|
||||
|
||||
fc.all, fc.lastMod = all, newLastMod
|
||||
t3 := time.Now()
|
||||
|
||||
// Report on the scanning stats and return
|
||||
log.Debug("FS scan times", "list", t1.Sub(t0), "set", t2.Sub(t1), "diff", t3.Sub(t2))
|
||||
return creates, deletes, updates, nil
|
||||
}
|
||||
|
||||
// skipKeyFile ignores editor backups, hidden files and folders/symlinks.
|
||||
func skipKeyFile(fi os.FileInfo) bool {
|
||||
// Skip editor backups and UNIX-style hidden files.
|
||||
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
||||
return true
|
||||
}
|
||||
// Skip misc special files, directories (yes, symlinks too).
|
||||
if fi.IsDir() || fi.Mode()&os.ModeType != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,220 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
version = 3
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
Id uuid.UUID // Version 4 "random" for unique id not derived from key data
|
||||
// to simplify lookups we also store the address
|
||||
Address common.Address
|
||||
// we only store privkey as pubkey/address can be derived from it
|
||||
// privkey in this struct is always in plaintext
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
type keyStore interface {
|
||||
// Loads and decrypts the key from disk.
|
||||
GetKey(addr common.Address, filename string, auth string) (*Key, error)
|
||||
// Writes and encrypts the key.
|
||||
StoreKey(filename string, k *Key, auth string) error
|
||||
// Joins filename with the key directory unless it is already absolute.
|
||||
JoinPath(filename string) string
|
||||
}
|
||||
|
||||
type plainKeyJSON struct {
|
||||
Address string `json:"address"`
|
||||
PrivateKey string `json:"privatekey"`
|
||||
Id string `json:"id"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type encryptedKeyJSONV3 struct {
|
||||
Address string `json:"address"`
|
||||
Crypto cryptoJSON `json:"crypto"`
|
||||
Id string `json:"id"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type encryptedKeyJSONV1 struct {
|
||||
Address string `json:"address"`
|
||||
Crypto cryptoJSON `json:"crypto"`
|
||||
Id string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type cryptoJSON struct {
|
||||
Cipher string `json:"cipher"`
|
||||
CipherText string `json:"ciphertext"`
|
||||
CipherParams cipherparamsJSON `json:"cipherparams"`
|
||||
KDF string `json:"kdf"`
|
||||
KDFParams map[string]interface{} `json:"kdfparams"`
|
||||
MAC string `json:"mac"`
|
||||
}
|
||||
|
||||
type cipherparamsJSON struct {
|
||||
IV string `json:"iv"`
|
||||
}
|
||||
|
||||
func (k *Key) MarshalJSON() (j []byte, err error) {
|
||||
jStruct := plainKeyJSON{
|
||||
hex.EncodeToString(k.Address[:]),
|
||||
hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)),
|
||||
k.Id.String(),
|
||||
version,
|
||||
}
|
||||
j, err = json.Marshal(jStruct)
|
||||
return j, err
|
||||
}
|
||||
|
||||
func (k *Key) UnmarshalJSON(j []byte) (err error) {
|
||||
keyJSON := new(plainKeyJSON)
|
||||
err = json.Unmarshal(j, &keyJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u := new(uuid.UUID)
|
||||
*u = uuid.Parse(keyJSON.Id)
|
||||
k.Id = *u
|
||||
addr, err := hex.DecodeString(keyJSON.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
k.Address = common.BytesToAddress(addr)
|
||||
k.PrivateKey = privkey
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newKeyFromECDSA(privateKeyECDSA *ecdsa.PrivateKey) *Key {
|
||||
id := uuid.NewRandom()
|
||||
key := &Key{
|
||||
Id: id,
|
||||
Address: crypto.PubkeyToAddress(privateKeyECDSA.PublicKey),
|
||||
PrivateKey: privateKeyECDSA,
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// NewKeyForDirectICAP generates a key whose address fits into < 155 bits so it can fit
|
||||
// into the Direct ICAP spec. for simplicity and easier compatibility with other libs, we
|
||||
// retry until the first byte is 0.
|
||||
func NewKeyForDirectICAP(rand io.Reader) *Key {
|
||||
randBytes := make([]byte, 64)
|
||||
_, err := rand.Read(randBytes)
|
||||
if err != nil {
|
||||
panic("key generation: could not read from random source: " + err.Error())
|
||||
}
|
||||
reader := bytes.NewReader(randBytes)
|
||||
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), reader)
|
||||
if err != nil {
|
||||
panic("key generation: ecdsa.GenerateKey failed: " + err.Error())
|
||||
}
|
||||
key := newKeyFromECDSA(privateKeyECDSA)
|
||||
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
|
||||
return NewKeyForDirectICAP(rand)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func newKey(rand io.Reader) (*Key, error) {
|
||||
privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), rand)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newKeyFromECDSA(privateKeyECDSA), nil
|
||||
}
|
||||
|
||||
func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Account, error) {
|
||||
key, err := newKey(rand)
|
||||
if err != nil {
|
||||
return nil, accounts.Account{}, err
|
||||
}
|
||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}}
|
||||
if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
|
||||
zeroKey(key.PrivateKey)
|
||||
return nil, a, err
|
||||
}
|
||||
return key, a, err
|
||||
}
|
||||
|
||||
func writeKeyFile(file string, content []byte) error {
|
||||
// Create the keystore directory with appropriate permissions
|
||||
// in case it is not present yet.
|
||||
const dirPerm = 0700
|
||||
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
// Atomic write: create a temporary hidden file first
|
||||
// then move it into place. TempFile assigns mode 0600.
|
||||
f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := f.Write(content); err != nil {
|
||||
f.Close()
|
||||
os.Remove(f.Name())
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
return os.Rename(f.Name(), file)
|
||||
}
|
||||
|
||||
// keyFileName implements the naming convention for keyfiles:
|
||||
// UTC--<created_at UTC ISO8601>-<address hex>
|
||||
func keyFileName(keyAddr common.Address) string {
|
||||
ts := time.Now().UTC()
|
||||
return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:]))
|
||||
}
|
||||
|
||||
func toISO8601(t time.Time) string {
|
||||
var tz string
|
||||
name, offset := t.Zone()
|
||||
if name == "UTC" {
|
||||
tz = "Z"
|
||||
} else {
|
||||
tz = fmt.Sprintf("%03d00", offset/3600)
|
||||
}
|
||||
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
||||
}
|
||||
@@ -1,493 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package keystore implements encrypted storage of secp256k1 private keys.
|
||||
//
|
||||
// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
|
||||
// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
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 passphrase")
|
||||
)
|
||||
|
||||
// KeyStoreType is the reflect type of a keystore backend.
|
||||
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
||||
|
||||
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
const KeyStoreScheme = "keystore"
|
||||
|
||||
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
||||
const walletRefreshCycle = 3 * time.Second
|
||||
|
||||
// KeyStore manages a key storage directory on disk.
|
||||
type KeyStore struct {
|
||||
storage keyStore // Storage backend, might be cleartext or encrypted
|
||||
cache *accountCache // In-memory account cache over the filesystem storage
|
||||
changes chan struct{} // Channel receiving change notifications from the cache
|
||||
unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
|
||||
|
||||
wallets []accounts.Wallet // Wallet wrappers around the individual key files
|
||||
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||
updating bool // Whether the event notification loop is running
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type unlocked struct {
|
||||
*Key
|
||||
abort chan struct{}
|
||||
}
|
||||
|
||||
// NewKeyStore creates a keystore for the given directory.
|
||||
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
||||
keydir, _ = filepath.Abs(keydir)
|
||||
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}}
|
||||
ks.init(keydir)
|
||||
return ks
|
||||
}
|
||||
|
||||
// NewPlaintextKeyStore creates a keystore for the given directory.
|
||||
// Deprecated: Use NewKeyStore.
|
||||
func NewPlaintextKeyStore(keydir string) *KeyStore {
|
||||
keydir, _ = filepath.Abs(keydir)
|
||||
ks := &KeyStore{storage: &keyStorePlain{keydir}}
|
||||
ks.init(keydir)
|
||||
return ks
|
||||
}
|
||||
|
||||
func (ks *KeyStore) init(keydir string) {
|
||||
// Lock the mutex since the account cache might call back with events
|
||||
ks.mu.Lock()
|
||||
defer ks.mu.Unlock()
|
||||
|
||||
// Initialize the set of unlocked keys and the account cache
|
||||
ks.unlocked = make(map[common.Address]*unlocked)
|
||||
ks.cache, ks.changes = newAccountCache(keydir)
|
||||
|
||||
// TODO: In order for this finalizer to work, there must be no references
|
||||
// to ks. addressCache doesn't keep a reference but unlocked keys do,
|
||||
// so the finalizer will not trigger until all timed unlocks have expired.
|
||||
runtime.SetFinalizer(ks, func(m *KeyStore) {
|
||||
m.cache.close()
|
||||
})
|
||||
// Create the initial list of wallets from the cache
|
||||
accs := ks.cache.accounts()
|
||||
ks.wallets = make([]accounts.Wallet, len(accs))
|
||||
for i := 0; i < len(accs); i++ {
|
||||
ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
|
||||
}
|
||||
}
|
||||
|
||||
// Wallets implements accounts.Backend, returning all single-key wallets from the
|
||||
// keystore directory.
|
||||
func (ks *KeyStore) Wallets() []accounts.Wallet {
|
||||
// Make sure the list of wallets is in sync with the account cache
|
||||
ks.refreshWallets()
|
||||
|
||||
ks.mu.RLock()
|
||||
defer ks.mu.RUnlock()
|
||||
|
||||
cpy := make([]accounts.Wallet, len(ks.wallets))
|
||||
copy(cpy, ks.wallets)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// refreshWallets retrieves the current account list and based on that does any
|
||||
// necessary wallet refreshes.
|
||||
func (ks *KeyStore) refreshWallets() {
|
||||
// Retrieve the current list of accounts
|
||||
ks.mu.Lock()
|
||||
accs := ks.cache.accounts()
|
||||
|
||||
// Transform the current list of wallets into the new one
|
||||
wallets := make([]accounts.Wallet, 0, len(accs))
|
||||
events := []accounts.WalletEvent{}
|
||||
|
||||
for _, account := range accs {
|
||||
// Drop wallets while they were in front of the next account
|
||||
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
|
||||
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
|
||||
ks.wallets = ks.wallets[1:]
|
||||
}
|
||||
// If there are no more wallets or the account is before the next, wrap new wallet
|
||||
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
|
||||
wallet := &keystoreWallet{account: account, keystore: ks}
|
||||
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||
wallets = append(wallets, wallet)
|
||||
continue
|
||||
}
|
||||
// If the account is the same as the first wallet, keep it
|
||||
if ks.wallets[0].Accounts()[0] == account {
|
||||
wallets = append(wallets, ks.wallets[0])
|
||||
ks.wallets = ks.wallets[1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Drop any leftover wallets and set the new batch
|
||||
for _, wallet := range ks.wallets {
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||
}
|
||||
ks.wallets = wallets
|
||||
ks.mu.Unlock()
|
||||
|
||||
// Fire all wallet events and return
|
||||
for _, event := range events {
|
||||
ks.updateFeed.Send(event)
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||
// receive notifications on the addition or removal of keystore wallets.
|
||||
func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||
// We need the mutex to reliably start/stop the update loop
|
||||
ks.mu.Lock()
|
||||
defer ks.mu.Unlock()
|
||||
|
||||
// Subscribe the caller and track the subscriber count
|
||||
sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
|
||||
|
||||
// Subscribers require an active notification loop, start it
|
||||
if !ks.updating {
|
||||
ks.updating = true
|
||||
go ks.updater()
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
// updater is responsible for maintaining an up-to-date list of wallets stored in
|
||||
// the keystore, and for firing wallet addition/removal events. It listens for
|
||||
// account change events from the underlying account cache, and also periodically
|
||||
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
||||
// is not running).
|
||||
func (ks *KeyStore) updater() {
|
||||
for {
|
||||
// Wait for an account update or a refresh timeout
|
||||
select {
|
||||
case <-ks.changes:
|
||||
case <-time.After(walletRefreshCycle):
|
||||
}
|
||||
// Run the wallet refresher
|
||||
ks.refreshWallets()
|
||||
|
||||
// If all our subscribers left, stop the updater
|
||||
ks.mu.Lock()
|
||||
if ks.updateScope.Count() == 0 {
|
||||
ks.updating = false
|
||||
ks.mu.Unlock()
|
||||
return
|
||||
}
|
||||
ks.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// HasAddress reports whether a key with the given address is present.
|
||||
func (ks *KeyStore) HasAddress(addr common.Address) bool {
|
||||
return ks.cache.hasAddress(addr)
|
||||
}
|
||||
|
||||
// Accounts returns all key files present in the directory.
|
||||
func (ks *KeyStore) Accounts() []accounts.Account {
|
||||
return ks.cache.accounts()
|
||||
}
|
||||
|
||||
// Delete deletes the key matched by account if the passphrase is correct.
|
||||
// If the account contains no filename, the address must match a unique key.
|
||||
func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
|
||||
// Decrypting the key isn't really necessary, but we do
|
||||
// it anyway to check the password and zero out the key
|
||||
// immediately afterwards.
|
||||
a, key, err := ks.getDecryptedKey(a, passphrase)
|
||||
if key != nil {
|
||||
zeroKey(key.PrivateKey)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The order is crucial here. The key is dropped from the
|
||||
// cache after the file is gone so that a reload happening in
|
||||
// between won't insert it into the cache again.
|
||||
err = os.Remove(a.URL.Path)
|
||||
if err == nil {
|
||||
ks.cache.delete(a)
|
||||
ks.refreshWallets()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SignHash calculates a ECDSA signature for the given hash. The produced
|
||||
// signature is in the [R || S || V] format where V is 0 or 1.
|
||||
func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
|
||||
// Look up the key to sign with and abort if it cannot be found
|
||||
ks.mu.RLock()
|
||||
defer ks.mu.RUnlock()
|
||||
|
||||
unlockedKey, found := ks.unlocked[a.Address]
|
||||
if !found {
|
||||
return nil, ErrLocked
|
||||
}
|
||||
// Sign the hash using plain ECDSA operations
|
||||
return crypto.Sign(hash, unlockedKey.PrivateKey)
|
||||
}
|
||||
|
||||
// SignTx signs the given transaction with the requested account.
|
||||
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
// Look up the key to sign with and abort if it cannot be found
|
||||
ks.mu.RLock()
|
||||
defer ks.mu.RUnlock()
|
||||
|
||||
unlockedKey, found := ks.unlocked[a.Address]
|
||||
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)
|
||||
}
|
||||
|
||||
// SignHashWithPassphrase signs hash if the private key matching the given address
|
||||
// can be decrypted with the given passphrase. The produced signature is in the
|
||||
// [R || S || V] format where V is 0 or 1.
|
||||
func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
|
||||
_, key, err := ks.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer zeroKey(key.PrivateKey)
|
||||
return crypto.Sign(hash, key.PrivateKey)
|
||||
}
|
||||
|
||||
// SignTxWithPassphrase signs the transaction if the private key matching the
|
||||
// given address can be decrypted with the given passphrase.
|
||||
func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
_, key, err := ks.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// Unlock unlocks the given account indefinitely.
|
||||
func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
|
||||
return ks.TimedUnlock(a, passphrase, 0)
|
||||
}
|
||||
|
||||
// Lock removes the private key with the given address from memory.
|
||||
func (ks *KeyStore) Lock(addr common.Address) error {
|
||||
ks.mu.Lock()
|
||||
if unl, found := ks.unlocked[addr]; found {
|
||||
ks.mu.Unlock()
|
||||
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
||||
} else {
|
||||
ks.mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimedUnlock unlocks the given account with the passphrase. The account
|
||||
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
|
||||
// until the program exits. The account must match a unique key file.
|
||||
//
|
||||
// If the account address is already unlocked for a duration, TimedUnlock extends or
|
||||
// shortens the active unlock timeout. If the address was previously unlocked
|
||||
// indefinitely the timeout is not altered.
|
||||
func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
|
||||
a, key, err := ks.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ks.mu.Lock()
|
||||
defer ks.mu.Unlock()
|
||||
u, found := ks.unlocked[a.Address]
|
||||
if found {
|
||||
if u.abort == nil {
|
||||
// The address was unlocked indefinitely, so unlocking
|
||||
// it with a timeout would be confusing.
|
||||
zeroKey(key.PrivateKey)
|
||||
return nil
|
||||
}
|
||||
// Terminate the expire goroutine and replace it below.
|
||||
close(u.abort)
|
||||
}
|
||||
if timeout > 0 {
|
||||
u = &unlocked{Key: key, abort: make(chan struct{})}
|
||||
go ks.expire(a.Address, u, timeout)
|
||||
} else {
|
||||
u = &unlocked{Key: key}
|
||||
}
|
||||
ks.unlocked[a.Address] = u
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find resolves the given account into a unique entry in the keystore.
|
||||
func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) {
|
||||
ks.cache.maybeReload()
|
||||
ks.cache.mu.Lock()
|
||||
a, err := ks.cache.find(a)
|
||||
ks.cache.mu.Unlock()
|
||||
return a, err
|
||||
}
|
||||
|
||||
func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
|
||||
a, err := ks.Find(a)
|
||||
if err != nil {
|
||||
return a, nil, err
|
||||
}
|
||||
key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
|
||||
return a, key, err
|
||||
}
|
||||
|
||||
func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
|
||||
t := time.NewTimer(timeout)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-u.abort:
|
||||
// just quit
|
||||
case <-t.C:
|
||||
ks.mu.Lock()
|
||||
// only drop if it's still the same key instance that dropLater
|
||||
// was launched with. we can check that using pointer equality
|
||||
// because the map stores a new pointer every time the key is
|
||||
// unlocked.
|
||||
if ks.unlocked[addr] == u {
|
||||
zeroKey(u.PrivateKey)
|
||||
delete(ks.unlocked, addr)
|
||||
}
|
||||
ks.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccount generates a new key and stores it into the key directory,
|
||||
// encrypting it with the passphrase.
|
||||
func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
|
||||
_, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
|
||||
if err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
// Add the account to the cache immediately rather
|
||||
// than waiting for file system notifications to pick it up.
|
||||
ks.cache.add(account)
|
||||
ks.refreshWallets()
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Export exports as a JSON key, encrypted with newPassphrase.
|
||||
func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
|
||||
_, key, err := ks.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var N, P int
|
||||
if store, ok := ks.storage.(*keyStorePassphrase); ok {
|
||||
N, P = store.scryptN, store.scryptP
|
||||
} else {
|
||||
N, P = StandardScryptN, StandardScryptP
|
||||
}
|
||||
return EncryptKey(key, newPassphrase, N, P)
|
||||
}
|
||||
|
||||
// Import stores the given encrypted JSON key into the key directory.
|
||||
func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
|
||||
key, err := DecryptKey(keyJSON, passphrase)
|
||||
if key != nil && key.PrivateKey != nil {
|
||||
defer zeroKey(key.PrivateKey)
|
||||
}
|
||||
if err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
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) {
|
||||
key := newKeyFromECDSA(priv)
|
||||
if ks.cache.hasAddress(key.Address) {
|
||||
return accounts.Account{}, fmt.Errorf("account already exists")
|
||||
}
|
||||
return ks.importKey(key, passphrase)
|
||||
}
|
||||
|
||||
func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
|
||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
|
||||
if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
ks.cache.add(a)
|
||||
ks.refreshWallets()
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Update changes the passphrase of an existing account.
|
||||
func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error {
|
||||
a, key, err := ks.getDecryptedKey(a, passphrase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
|
||||
}
|
||||
|
||||
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
|
||||
// a key file in the key directory. The key file is encrypted with the same passphrase.
|
||||
func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
|
||||
a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
ks.cache.add(a)
|
||||
ks.refreshWallets()
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// zeroKey zeroes a private key in memory.
|
||||
func zeroKey(k *ecdsa.PrivateKey) {
|
||||
b := k.D.Bits()
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -1,312 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
|
||||
This key store behaves as KeyStorePlain with the difference that
|
||||
the private key is encrypted and on disk uses another JSON encoding.
|
||||
|
||||
The crypto is documented at https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||
|
||||
*/
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
keyHeaderKDF = "scrypt"
|
||||
|
||||
// StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB
|
||||
// memory and taking approximately 1s CPU time on a modern processor.
|
||||
StandardScryptN = 1 << 18
|
||||
|
||||
// StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB
|
||||
// memory and taking approximately 1s CPU time on a modern processor.
|
||||
StandardScryptP = 1
|
||||
|
||||
// LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB
|
||||
// memory and taking approximately 100ms CPU time on a modern processor.
|
||||
LightScryptN = 1 << 12
|
||||
|
||||
// LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB
|
||||
// memory and taking approximately 100ms CPU time on a modern processor.
|
||||
LightScryptP = 6
|
||||
|
||||
scryptR = 8
|
||||
scryptDKLen = 32
|
||||
)
|
||||
|
||||
type keyStorePassphrase struct {
|
||||
keysDirPath string
|
||||
scryptN int
|
||||
scryptP int
|
||||
}
|
||||
|
||||
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
||||
// Load the key from the keystore and decrypt its contents
|
||||
keyjson, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := DecryptKey(keyjson, auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Make sure we're really operating on the requested key (no swap attacks)
|
||||
if key.Address != addr {
|
||||
return nil, fmt.Errorf("key content mismatch: have account %x, want %x", key.Address, addr)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
||||
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
|
||||
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth)
|
||||
return a.Address, err
|
||||
}
|
||||
|
||||
func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) error {
|
||||
keyjson, err := EncryptKey(key, auth, ks.scryptN, ks.scryptP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeKeyFile(filename, keyjson)
|
||||
}
|
||||
|
||||
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||
if filepath.IsAbs(filename) {
|
||||
return filename
|
||||
}
|
||||
return filepath.Join(ks.keysDirPath, filename)
|
||||
}
|
||||
|
||||
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
||||
// blob that can be decrypted later on.
|
||||
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||
authArray := []byte(auth)
|
||||
salt := randentropy.GetEntropyCSPRNG(32)
|
||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptKey := derivedKey[:16]
|
||||
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
||||
|
||||
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
|
||||
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||
|
||||
scryptParamsJSON := make(map[string]interface{}, 5)
|
||||
scryptParamsJSON["n"] = scryptN
|
||||
scryptParamsJSON["r"] = scryptR
|
||||
scryptParamsJSON["p"] = scryptP
|
||||
scryptParamsJSON["dklen"] = scryptDKLen
|
||||
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
||||
|
||||
cipherParamsJSON := cipherparamsJSON{
|
||||
IV: hex.EncodeToString(iv),
|
||||
}
|
||||
|
||||
cryptoStruct := cryptoJSON{
|
||||
Cipher: "aes-128-ctr",
|
||||
CipherText: hex.EncodeToString(cipherText),
|
||||
CipherParams: cipherParamsJSON,
|
||||
KDF: keyHeaderKDF,
|
||||
KDFParams: scryptParamsJSON,
|
||||
MAC: hex.EncodeToString(mac),
|
||||
}
|
||||
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
||||
hex.EncodeToString(key.Address[:]),
|
||||
cryptoStruct,
|
||||
key.Id.String(),
|
||||
version,
|
||||
}
|
||||
return json.Marshal(encryptedKeyJSONV3)
|
||||
}
|
||||
|
||||
// DecryptKey decrypts a key from a json blob, returning the private key itself.
|
||||
func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||
// Parse the json into a simple map to fetch the key version
|
||||
m := make(map[string]interface{})
|
||||
if err := json.Unmarshal(keyjson, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Depending on the version try to parse one way or another
|
||||
var (
|
||||
keyBytes, keyId []byte
|
||||
err error
|
||||
)
|
||||
if version, ok := m["version"].(string); ok && version == "1" {
|
||||
k := new(encryptedKeyJSONV1)
|
||||
if err := json.Unmarshal(keyjson, k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, keyId, err = decryptKeyV1(k, auth)
|
||||
} else {
|
||||
k := new(encryptedKeyJSONV3)
|
||||
if err := json.Unmarshal(keyjson, k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyBytes, keyId, err = decryptKeyV3(k, auth)
|
||||
}
|
||||
// Handle any decryption errors and return the key
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := crypto.ToECDSAUnsafe(keyBytes)
|
||||
|
||||
return &Key{
|
||||
Id: uuid.UUID(keyId),
|
||||
Address: crypto.PubkeyToAddress(key.PublicKey),
|
||||
PrivateKey: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||
if keyProtected.Version != version {
|
||||
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
|
||||
}
|
||||
|
||||
if keyProtected.Crypto.Cipher != "aes-128-ctr" {
|
||||
return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
|
||||
}
|
||||
|
||||
keyId = uuid.Parse(keyProtected.Id)
|
||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||
if !bytes.Equal(calculatedMAC, mac) {
|
||||
return nil, nil, ErrDecrypt
|
||||
}
|
||||
|
||||
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return plainText, keyId, err
|
||||
}
|
||||
|
||||
func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||
keyId = uuid.Parse(keyProtected.Id)
|
||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||
if !bytes.Equal(calculatedMAC, mac) {
|
||||
return nil, nil, ErrDecrypt
|
||||
}
|
||||
|
||||
plainText, err := aesCBCDecrypt(crypto.Keccak256(derivedKey[:16])[:16], cipherText, iv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return plainText, keyId, err
|
||||
}
|
||||
|
||||
func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
|
||||
authArray := []byte(auth)
|
||||
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
|
||||
|
||||
if cryptoJSON.KDF == keyHeaderKDF {
|
||||
n := ensureInt(cryptoJSON.KDFParams["n"])
|
||||
r := ensureInt(cryptoJSON.KDFParams["r"])
|
||||
p := ensureInt(cryptoJSON.KDFParams["p"])
|
||||
return scrypt.Key(authArray, salt, n, r, p, dkLen)
|
||||
|
||||
} else if cryptoJSON.KDF == "pbkdf2" {
|
||||
c := ensureInt(cryptoJSON.KDFParams["c"])
|
||||
prf := cryptoJSON.KDFParams["prf"].(string)
|
||||
if prf != "hmac-sha256" {
|
||||
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
|
||||
}
|
||||
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
|
||||
return key, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
|
||||
}
|
||||
|
||||
// TODO: can we do without this when unmarshalling dynamic JSON?
|
||||
// why do integers in KDF params end up as float64 and not int after
|
||||
// unmarshal?
|
||||
func ensureInt(x interface{}) int {
|
||||
res, ok := x.(int)
|
||||
if !ok {
|
||||
res = int(x.(float64))
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -1,60 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
const (
|
||||
veryLightScryptN = 2
|
||||
veryLightScryptP = 1
|
||||
)
|
||||
|
||||
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
||||
func TestKeyEncryptDecrypt(t *testing.T) {
|
||||
keyjson, err := ioutil.ReadFile("testdata/very-light-scrypt.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
password := ""
|
||||
address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8")
|
||||
|
||||
// Do a few rounds of decryption and encryption
|
||||
for i := 0; i < 3; i++ {
|
||||
// Try a bad password first
|
||||
if _, err := DecryptKey(keyjson, password+"bad"); err == nil {
|
||||
t.Errorf("test %d: json key decrypted with bad password", i)
|
||||
}
|
||||
// Decrypt with the correct password
|
||||
key, err := DecryptKey(keyjson, password)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: json key failed to decrypt: %v", i, err)
|
||||
}
|
||||
if key.Address != address {
|
||||
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
|
||||
}
|
||||
// Recrypt with a new password and start over
|
||||
password += "new data appended"
|
||||
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
||||
t.Errorf("test %d: failed to recrypt key %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,61 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
type keyStorePlain struct {
|
||||
keysDirPath string
|
||||
}
|
||||
|
||||
func (ks keyStorePlain) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fd.Close()
|
||||
key := new(Key)
|
||||
if err := json.NewDecoder(fd).Decode(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.Address != addr {
|
||||
return nil, fmt.Errorf("key content mismatch: have address %x, want %x", key.Address, addr)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
|
||||
content, err := json.Marshal(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writeKeyFile(filename, content)
|
||||
}
|
||||
|
||||
func (ks keyStorePlain) JoinPath(filename string) string {
|
||||
if filepath.IsAbs(filename) {
|
||||
return filename
|
||||
}
|
||||
return filepath.Join(ks.keysDirPath, filename)
|
||||
}
|
||||
@@ -1,266 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
||||
d, err := ioutil.TempDir("", "geth-keystore-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if encrypted {
|
||||
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP}
|
||||
} else {
|
||||
ks = &keyStorePlain{d}
|
||||
}
|
||||
return d, ks
|
||||
}
|
||||
|
||||
func TestKeyStorePlain(t *testing.T) {
|
||||
dir, ks := tmpKeyStoreIface(t, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "" // not used but required by API
|
||||
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(k1.Address, k2.Address) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyStorePassphrase(t *testing.T) {
|
||||
dir, ks := tmpKeyStoreIface(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "foo"
|
||||
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(k1.Address, k2.Address) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
||||
dir, ks := tmpKeyStoreIface(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "foo"
|
||||
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
|
||||
t.Fatalf("wrong error for invalid passphrase\ngot %q\nwant %q", err, ErrDecrypt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportPreSaleKey(t *testing.T) {
|
||||
dir, ks := tmpKeyStoreIface(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// file content of a presale key file generated with:
|
||||
// python pyethsaletool.py genwallet
|
||||
// with password "foo"
|
||||
fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
|
||||
pass := "foo"
|
||||
account, _, err := importPreSaleKey(ks, []byte(fileContent), pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") {
|
||||
t.Errorf("imported account has wrong address %x", account.Address)
|
||||
}
|
||||
if !strings.HasPrefix(account.URL.Path, dir) {
|
||||
t.Errorf("imported account file not in keystore directory: %q", account.URL)
|
||||
}
|
||||
}
|
||||
|
||||
// Test and utils for the key store tests in the Ethereum JSON tests;
|
||||
// testdataKeyStoreTests/basic_tests.json
|
||||
type KeyStoreTestV3 struct {
|
||||
Json encryptedKeyJSONV3
|
||||
Password string
|
||||
Priv string
|
||||
}
|
||||
|
||||
type KeyStoreTestV1 struct {
|
||||
Json encryptedKeyJSONV1
|
||||
Password string
|
||||
Priv string
|
||||
}
|
||||
|
||||
func TestV3_PBKDF2_1(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||
testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
|
||||
}
|
||||
|
||||
var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests")
|
||||
|
||||
func skipIfSubmoduleMissing(t *testing.T) {
|
||||
if !common.FileExist(testsSubmodule) {
|
||||
t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule)
|
||||
}
|
||||
}
|
||||
|
||||
func TestV3_PBKDF2_2(t *testing.T) {
|
||||
skipIfSubmoduleMissing(t)
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||
testDecryptV3(tests["test1"], t)
|
||||
}
|
||||
|
||||
func TestV3_PBKDF2_3(t *testing.T) {
|
||||
skipIfSubmoduleMissing(t)
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||
testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
|
||||
}
|
||||
|
||||
func TestV3_PBKDF2_4(t *testing.T) {
|
||||
skipIfSubmoduleMissing(t)
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||
testDecryptV3(tests["evilnonce"], t)
|
||||
}
|
||||
|
||||
func TestV3_Scrypt_1(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||
testDecryptV3(tests["wikipage_test_vector_scrypt"], t)
|
||||
}
|
||||
|
||||
func TestV3_Scrypt_2(t *testing.T) {
|
||||
skipIfSubmoduleMissing(t)
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||
testDecryptV3(tests["test2"], t)
|
||||
}
|
||||
|
||||
func TestV1_1(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t)
|
||||
testDecryptV1(tests["test1"], t)
|
||||
}
|
||||
|
||||
func TestV1_2(t *testing.T) {
|
||||
t.Parallel()
|
||||
ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP}
|
||||
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
|
||||
file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
|
||||
k, err := ks.GetKey(addr, file, "g")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey))
|
||||
expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
|
||||
if privHex != expectedHex {
|
||||
t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex))
|
||||
}
|
||||
}
|
||||
|
||||
func testDecryptV3(test KeyStoreTestV3, t *testing.T) {
|
||||
privBytes, _, err := decryptKeyV3(&test.Json, test.Password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privHex := hex.EncodeToString(privBytes)
|
||||
if test.Priv != privHex {
|
||||
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
|
||||
}
|
||||
}
|
||||
|
||||
func testDecryptV1(test KeyStoreTestV1, t *testing.T) {
|
||||
privBytes, _, err := decryptKeyV1(&test.Json, test.Password)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
privHex := hex.EncodeToString(privBytes)
|
||||
if test.Priv != privHex {
|
||||
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
|
||||
}
|
||||
}
|
||||
|
||||
func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 {
|
||||
tests := make(map[string]KeyStoreTestV3)
|
||||
err := common.LoadJSON(file, &tests)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tests
|
||||
}
|
||||
|
||||
func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 {
|
||||
tests := make(map[string]KeyStoreTestV1)
|
||||
err := common.LoadJSON(file, &tests)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tests
|
||||
}
|
||||
|
||||
func TestKeyForDirectICAP(t *testing.T) {
|
||||
t.Parallel()
|
||||
key := NewKeyForDirectICAP(rand.Reader)
|
||||
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
|
||||
t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
func TestV3_31_Byte_Key(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||
testDecryptV3(tests["31_byte_key"], t)
|
||||
}
|
||||
|
||||
func TestV3_30_Byte_Key(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||
testDecryptV3(tests["30_byte_key"], t)
|
||||
}
|
||||
@@ -1,387 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
var testSigData = make([]byte, 32)
|
||||
|
||||
func TestKeyStore(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
a, err := ks.NewAccount("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasPrefix(a.URL.Path, dir) {
|
||||
t.Errorf("account file %s doesn't have dir prefix", a.URL)
|
||||
}
|
||||
stat, err := os.Stat(a.URL.Path)
|
||||
if err != nil {
|
||||
t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
|
||||
}
|
||||
if runtime.GOOS != "windows" && stat.Mode() != 0600 {
|
||||
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
|
||||
}
|
||||
if !ks.HasAddress(a.Address) {
|
||||
t.Errorf("HasAccount(%x) should've returned true", a.Address)
|
||||
}
|
||||
if err := ks.Update(a, "foo", "bar"); err != nil {
|
||||
t.Errorf("Update error: %v", err)
|
||||
}
|
||||
if err := ks.Delete(a, "bar"); err != nil {
|
||||
t.Errorf("Delete error: %v", err)
|
||||
}
|
||||
if common.FileExist(a.URL.Path) {
|
||||
t.Errorf("account file %s should be gone after Delete", a.URL)
|
||||
}
|
||||
if ks.HasAddress(a.Address) {
|
||||
t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.NewAccount(pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignWithPassphrase(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "passwd"
|
||||
acc, err := ks.NewAccount(pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, unlocked := ks.unlocked[acc.Address]; unlocked {
|
||||
t.Fatal("expected account to be locked")
|
||||
}
|
||||
|
||||
_, err = ks.SignHashWithPassphrase(acc, pass, testSigData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, unlocked := ks.unlocked[acc.Address]; unlocked {
|
||||
t.Fatal("expected account to be locked")
|
||||
}
|
||||
|
||||
if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil {
|
||||
t.Fatal("expected SignHashWithPassphrase to fail with invalid password")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimedUnlock(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "foo"
|
||||
a1, err := ks.NewAccount(pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Signing without passphrase fails because account is locked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||
if err != ErrLocked {
|
||||
t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
|
||||
}
|
||||
|
||||
// Signing with passphrase works
|
||||
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Signing without passphrase works because account is temp unlocked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||
if err != nil {
|
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||
}
|
||||
|
||||
// Signing fails again after automatic locking
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||
if err != ErrLocked {
|
||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideUnlock(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "foo"
|
||||
a1, err := ks.NewAccount(pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Unlock indefinitely.
|
||||
if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Signing without passphrase works because account is temp unlocked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||
if err != nil {
|
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||
}
|
||||
|
||||
// reset unlock to a shorter period, invalidates the previous unlock
|
||||
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Signing without passphrase still works because account is temp unlocked
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||
if err != nil {
|
||||
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||
}
|
||||
|
||||
// Signing fails again after automatic locking
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||
if err != ErrLocked {
|
||||
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||
}
|
||||
}
|
||||
|
||||
// This test should fail under -race if signing races the expiration goroutine.
|
||||
func TestSignRace(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Create a test account.
|
||||
a1, err := ks.NewAccount("")
|
||||
if err != nil {
|
||||
t.Fatal("could not create the test account", err)
|
||||
}
|
||||
|
||||
if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil {
|
||||
t.Fatal("could not unlock the test account", err)
|
||||
}
|
||||
end := time.Now().Add(500 * time.Millisecond)
|
||||
for time.Now().Before(end) {
|
||||
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked {
|
||||
return
|
||||
} else if err != nil {
|
||||
t.Errorf("Sign error: %v", err)
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
t.Errorf("Account did not lock within the timeout")
|
||||
}
|
||||
|
||||
// Tests that the wallet notifier loop starts and stops correctly based on the
|
||||
// addition and removal of wallet event subscriptions.
|
||||
func TestWalletNotifierLifecycle(t *testing.T) {
|
||||
// Create a temporary kesytore to test with
|
||||
dir, ks := tmpKeyStore(t, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Ensure that the notification updater is not running yet
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
ks.mu.RLock()
|
||||
updating := ks.updating
|
||||
ks.mu.RUnlock()
|
||||
|
||||
if updating {
|
||||
t.Errorf("wallet notifier running without subscribers")
|
||||
}
|
||||
// Subscribe to the wallet feed and ensure the updater boots up
|
||||
updates := make(chan accounts.WalletEvent)
|
||||
|
||||
subs := make([]event.Subscription, 2)
|
||||
for i := 0; i < len(subs); i++ {
|
||||
// Create a new subscription
|
||||
subs[i] = ks.Subscribe(updates)
|
||||
|
||||
// Ensure the notifier comes online
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
ks.mu.RLock()
|
||||
updating = ks.updating
|
||||
ks.mu.RUnlock()
|
||||
|
||||
if !updating {
|
||||
t.Errorf("sub %d: wallet notifier not running after subscription", i)
|
||||
}
|
||||
}
|
||||
// Unsubscribe and ensure the updater terminates eventually
|
||||
for i := 0; i < len(subs); i++ {
|
||||
// Close an existing subscription
|
||||
subs[i].Unsubscribe()
|
||||
|
||||
// Ensure the notifier shuts down at and only at the last close
|
||||
for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
|
||||
ks.mu.RLock()
|
||||
updating = ks.updating
|
||||
ks.mu.RUnlock()
|
||||
|
||||
if i < len(subs)-1 && !updating {
|
||||
t.Fatalf("sub %d: event notifier stopped prematurely", i)
|
||||
}
|
||||
if i == len(subs)-1 && !updating {
|
||||
return
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
t.Errorf("wallet notifier didn't terminate after unsubscribe")
|
||||
}
|
||||
|
||||
type walletEvent struct {
|
||||
accounts.WalletEvent
|
||||
a accounts.Account
|
||||
}
|
||||
|
||||
// Tests that wallet notifications and correctly fired when accounts are added
|
||||
// or deleted from the keystore.
|
||||
func TestWalletNotifications(t *testing.T) {
|
||||
dir, ks := tmpKeyStore(t, false)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Subscribe to the wallet feed and collect events.
|
||||
var (
|
||||
events []walletEvent
|
||||
updates = make(chan accounts.WalletEvent)
|
||||
sub = ks.Subscribe(updates)
|
||||
)
|
||||
defer sub.Unsubscribe()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case ev := <-updates:
|
||||
events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
|
||||
case <-sub.Err():
|
||||
close(updates)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Randomly add and remove accounts.
|
||||
var (
|
||||
live = make(map[common.Address]accounts.Account)
|
||||
wantEvents []walletEvent
|
||||
)
|
||||
for i := 0; i < 1024; i++ {
|
||||
if create := len(live) == 0 || rand.Int()%4 > 0; create {
|
||||
// Add a new account and ensure wallet notifications arrives
|
||||
account, err := ks.NewAccount("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create test account: %v", err)
|
||||
}
|
||||
live[account.Address] = account
|
||||
wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account})
|
||||
} else {
|
||||
// Delete a random account.
|
||||
var account accounts.Account
|
||||
for _, a := range live {
|
||||
account = a
|
||||
break
|
||||
}
|
||||
if err := ks.Delete(account, ""); err != nil {
|
||||
t.Fatalf("failed to delete test account: %v", err)
|
||||
}
|
||||
delete(live, account.Address)
|
||||
wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account})
|
||||
}
|
||||
}
|
||||
|
||||
// Shut down the event collector and check events.
|
||||
sub.Unsubscribe()
|
||||
<-updates
|
||||
checkAccounts(t, live, ks.Wallets())
|
||||
checkEvents(t, wantEvents, events)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live))
|
||||
return
|
||||
}
|
||||
liveList := make([]accounts.Account, 0, len(live))
|
||||
for _, account := range live {
|
||||
liveList = append(liveList, account)
|
||||
}
|
||||
sort.Sort(accountsByURL(liveList))
|
||||
for j, wallet := range wallets {
|
||||
if accs := wallet.Accounts(); len(accs) != 1 {
|
||||
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
||||
} else if accs[0] != liveList[j] {
|
||||
t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times.
|
||||
func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
|
||||
for _, wantEv := range want {
|
||||
nmatch := 0
|
||||
for ; len(have) > 0; nmatch++ {
|
||||
if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a {
|
||||
break
|
||||
}
|
||||
have = have[1:]
|
||||
}
|
||||
if nmatch == 0 {
|
||||
t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
|
||||
d, err := ioutil.TempDir("", "eth-keystore-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
new := NewPlaintextKeyStore
|
||||
if encrypted {
|
||||
new = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
||||
}
|
||||
return d, new(d)
|
||||
}
|
||||
@@ -1,139 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// keystoreWallet implements the accounts.Wallet interface for the original
|
||||
// keystore.
|
||||
type keystoreWallet struct {
|
||||
account accounts.Account // Single account contained in this wallet
|
||||
keystore *KeyStore // Keystore where the account originates from
|
||||
}
|
||||
|
||||
// URL implements accounts.Wallet, returning the URL of the account within.
|
||||
func (w *keystoreWallet) URL() accounts.URL {
|
||||
return w.account.URL
|
||||
}
|
||||
|
||||
// Status implements accounts.Wallet, returning whether the account held by the
|
||||
// keystore wallet is unlocked or not.
|
||||
func (w *keystoreWallet) Status() (string, error) {
|
||||
w.keystore.mu.RLock()
|
||||
defer w.keystore.mu.RUnlock()
|
||||
|
||||
if _, ok := w.keystore.unlocked[w.account.Address]; ok {
|
||||
return "Unlocked", nil
|
||||
}
|
||||
return "Locked", nil
|
||||
}
|
||||
|
||||
// Open implements accounts.Wallet, but is a noop for plain wallets since there
|
||||
// is no connection or decryption step necessary to access the list of accounts.
|
||||
func (w *keystoreWallet) Open(passphrase string) error { return nil }
|
||||
|
||||
// Close implements accounts.Wallet, but is a noop for plain wallets since is no
|
||||
// meaningful open operation.
|
||||
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.
|
||||
func (w *keystoreWallet) Accounts() []accounts.Account {
|
||||
return []accounts.Account{w.account}
|
||||
}
|
||||
|
||||
// Contains implements accounts.Wallet, returning whether a particular account is
|
||||
// or is not wrapped by this wallet instance.
|
||||
func (w *keystoreWallet) Contains(account accounts.Account) bool {
|
||||
return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL)
|
||||
}
|
||||
|
||||
// Derive implements accounts.Wallet, but is a noop for plain wallets since there
|
||||
// is no notion of hierarchical account derivation for plain keystore accounts.
|
||||
func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||
return accounts.Account{}, accounts.ErrNotSupported
|
||||
}
|
||||
|
||||
// SelfDerive implements accounts.Wallet, but is a noop for plain wallets since
|
||||
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
||||
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {}
|
||||
|
||||
// SignHash implements accounts.Wallet, attempting to sign the given hash with
|
||||
// the given account. If the wallet does not wrap this particular account, an
|
||||
// error is returned to avoid account leakage (even though in theory we may be
|
||||
// able to sign via our shared keystore backend).
|
||||
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignHash(account, hash)
|
||||
}
|
||||
|
||||
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
||||
// with the given account. If the wallet does not wrap this particular account,
|
||||
// an error is returned to avoid account leakage (even though in theory we may
|
||||
// be able to sign via our shared keystore backend).
|
||||
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignTx(account, tx, chainID)
|
||||
}
|
||||
|
||||
// SignHashWithPassphrase implements accounts.Wallet, attempting to sign the
|
||||
// given hash with the given account using passphrase as extra authentication.
|
||||
func (w *keystoreWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignHashWithPassphrase(account, passphrase, hash)
|
||||
}
|
||||
|
||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||
// transaction with the given account using passphrase as extra authentication.
|
||||
func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
// Make sure the requested account is contained within
|
||||
if account.Address != w.account.Address {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
||||
return nil, accounts.ErrUnknownAccount
|
||||
}
|
||||
// Account seems valid, request the keystore to sign
|
||||
return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)
|
||||
}
|
||||
@@ -1,141 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/pborman/uuid"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// creates a Key and stores that in the given KeyStore by decrypting a presale key JSON
|
||||
func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accounts.Account, *Key, error) {
|
||||
key, err := decryptPreSaleKey(keyJSON, password)
|
||||
if err != nil {
|
||||
return accounts.Account{}, nil, err
|
||||
}
|
||||
key.Id = uuid.NewRandom()
|
||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: keyStore.JoinPath(keyFileName(key.Address))}}
|
||||
err = keyStore.StoreKey(a.URL.Path, key, password)
|
||||
return a, key, err
|
||||
}
|
||||
|
||||
func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error) {
|
||||
preSaleKeyStruct := struct {
|
||||
EncSeed string
|
||||
EthAddr string
|
||||
Email string
|
||||
BtcAddr string
|
||||
}{}
|
||||
err = json.Unmarshal(fileContent, &preSaleKeyStruct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encSeedBytes, err := hex.DecodeString(preSaleKeyStruct.EncSeed)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid hex in encSeed")
|
||||
}
|
||||
if len(encSeedBytes) < 16 {
|
||||
return nil, errors.New("invalid encSeed, too short")
|
||||
}
|
||||
iv := encSeedBytes[:16]
|
||||
cipherText := encSeedBytes[16:]
|
||||
/*
|
||||
See https://github.com/ethereum/pyethsaletool
|
||||
|
||||
pyethsaletool generates the encryption key from password by
|
||||
2000 rounds of PBKDF2 with HMAC-SHA-256 using password as salt (:().
|
||||
16 byte key length within PBKDF2 and resulting key is used as AES key
|
||||
*/
|
||||
passBytes := []byte(password)
|
||||
derivedKey := pbkdf2.Key(passBytes, passBytes, 2000, 16, sha256.New)
|
||||
plainText, err := aesCBCDecrypt(derivedKey, cipherText, iv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ethPriv := crypto.Keccak256(plainText)
|
||||
ecKey := crypto.ToECDSAUnsafe(ethPriv)
|
||||
|
||||
key = &Key{
|
||||
Id: nil,
|
||||
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
|
||||
PrivateKey: ecKey,
|
||||
}
|
||||
derivedAddr := hex.EncodeToString(key.Address.Bytes()) // needed because .Hex() gives leading "0x"
|
||||
expectedAddr := preSaleKeyStruct.EthAddr
|
||||
if derivedAddr != expectedAddr {
|
||||
err = fmt.Errorf("decrypted addr '%s' not equal to expected addr '%s'", derivedAddr, expectedAddr)
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
|
||||
// AES-128 is selected due to size of encryptKey.
|
||||
aesBlock, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream := cipher.NewCTR(aesBlock, iv)
|
||||
outText := make([]byte, len(inText))
|
||||
stream.XORKeyStream(outText, inText)
|
||||
return outText, err
|
||||
}
|
||||
|
||||
func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) {
|
||||
aesBlock, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decrypter := cipher.NewCBCDecrypter(aesBlock, iv)
|
||||
paddedPlaintext := make([]byte, len(cipherText))
|
||||
decrypter.CryptBlocks(paddedPlaintext, cipherText)
|
||||
plaintext := pkcs7Unpad(paddedPlaintext)
|
||||
if plaintext == nil {
|
||||
return nil, ErrDecrypt
|
||||
}
|
||||
return plaintext, err
|
||||
}
|
||||
|
||||
// From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes
|
||||
func pkcs7Unpad(in []byte) []byte {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
padding := in[len(in)-1]
|
||||
if int(padding) > len(in) || padding > aes.BlockSize {
|
||||
return nil
|
||||
} else if padding == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := len(in) - 1; i > len(in)-int(padding)-1; i-- {
|
||||
if in[i] != padding {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return in[:len(in)-int(padding)]
|
||||
}
|
||||
1
accounts/keystore/testdata/dupes/1
vendored
1
accounts/keystore/testdata/dupes/1
vendored
@@ -1 +0,0 @@
|
||||
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
||||
1
accounts/keystore/testdata/dupes/2
vendored
1
accounts/keystore/testdata/dupes/2
vendored
@@ -1 +0,0 @@
|
||||
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
||||
1
accounts/keystore/testdata/dupes/foo
vendored
1
accounts/keystore/testdata/dupes/foo
vendored
@@ -1 +0,0 @@
|
||||
{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
||||
21
accounts/keystore/testdata/keystore/README
vendored
21
accounts/keystore/testdata/keystore/README
vendored
@@ -1,21 +0,0 @@
|
||||
This directory contains accounts for testing.
|
||||
The passphrase that unlocks them is "foobar".
|
||||
|
||||
The "good" key files which are supposed to be loadable are:
|
||||
|
||||
- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
||||
Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
||||
- File: aaa
|
||||
Address: 0xf466859ead1932d743d622cb74fc058882e8648a
|
||||
- File: zzz
|
||||
Address: 0x289d485d9771714cce91d3393d764e1311907acc
|
||||
|
||||
The other files (including this README) are broken in various ways
|
||||
and should not be picked up by package accounts:
|
||||
|
||||
- File: no-address (missing address field, otherwise same as "aaa")
|
||||
- File: garbage (file with random data)
|
||||
- File: empty (file with no content)
|
||||
- File: swapfile~ (should be skipped)
|
||||
- File: .hiddenfile (should be skipped)
|
||||
- File: foo/... (should be skipped because it is a directory)
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
|
||||
1
accounts/keystore/testdata/keystore/aaa
vendored
1
accounts/keystore/testdata/keystore/aaa
vendored
@@ -1 +0,0 @@
|
||||
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3}
|
||||
BIN
accounts/keystore/testdata/keystore/garbage
vendored
BIN
accounts/keystore/testdata/keystore/garbage
vendored
Binary file not shown.
@@ -1 +0,0 @@
|
||||
{"crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
||||
1
accounts/keystore/testdata/keystore/zero
vendored
1
accounts/keystore/testdata/keystore/zero
vendored
@@ -1 +0,0 @@
|
||||
{"address":"0000000000000000000000000000000000000000","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
||||
1
accounts/keystore/testdata/keystore/zzz
vendored
1
accounts/keystore/testdata/keystore/zzz
vendored
@@ -1 +0,0 @@
|
||||
{"address":"289d485d9771714cce91d3393d764e1311907acc","crypto":{"cipher":"aes-128-ctr","ciphertext":"faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab","cipherparams":{"iv":"558833eec4a665a8c55608d7d503407d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04"},"mac":"21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe"},"id":"3279afcf-55ba-43ff-8997-02dcc46a6525","version":3}
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"}
|
||||
28
accounts/keystore/testdata/v1_test_vector.json
vendored
28
accounts/keystore/testdata/v1_test_vector.json
vendored
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"test1": {
|
||||
"json": {
|
||||
"Crypto": {
|
||||
"cipher": "aes-128-cbc",
|
||||
"cipherparams": {
|
||||
"iv": "35337770fc2117994ecdcad026bccff4"
|
||||
},
|
||||
"ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0",
|
||||
"kdf": "scrypt",
|
||||
"kdfparams": {
|
||||
"dklen": 32,
|
||||
"n": 262144,
|
||||
"p": 1,
|
||||
"r": 8,
|
||||
"salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"
|
||||
},
|
||||
"mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644",
|
||||
"version": "1"
|
||||
},
|
||||
"address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e",
|
||||
"id": "e25f7c1f-d318-4f29-b62c-687190d4d299",
|
||||
"version": "1"
|
||||
},
|
||||
"password": "g",
|
||||
"priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
|
||||
}
|
||||
}
|
||||
97
accounts/keystore/testdata/v3_test_vector.json
vendored
97
accounts/keystore/testdata/v3_test_vector.json
vendored
@@ -1,97 +0,0 @@
|
||||
{
|
||||
"wikipage_test_vector_scrypt": {
|
||||
"json": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {
|
||||
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
|
||||
},
|
||||
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
|
||||
"kdf" : "scrypt",
|
||||
"kdfparams" : {
|
||||
"dklen" : 32,
|
||||
"n" : 262144,
|
||||
"r" : 1,
|
||||
"p" : 8,
|
||||
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
|
||||
},
|
||||
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
|
||||
},
|
||||
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||
"version" : 3
|
||||
},
|
||||
"password": "testpassword",
|
||||
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||
},
|
||||
"wikipage_test_vector_pbkdf2": {
|
||||
"json": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {
|
||||
"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
|
||||
},
|
||||
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
|
||||
"kdf" : "pbkdf2",
|
||||
"kdfparams" : {
|
||||
"c" : 262144,
|
||||
"dklen" : 32,
|
||||
"prf" : "hmac-sha256",
|
||||
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
|
||||
},
|
||||
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
|
||||
},
|
||||
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||
"version" : 3
|
||||
},
|
||||
"password": "testpassword",
|
||||
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||
},
|
||||
"31_byte_key": {
|
||||
"json": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {
|
||||
"iv" : "e0c41130a323adc1446fc82f724bca2f"
|
||||
},
|
||||
"ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984",
|
||||
"kdf" : "scrypt",
|
||||
"kdfparams" : {
|
||||
"dklen" : 32,
|
||||
"n" : 2,
|
||||
"r" : 8,
|
||||
"p" : 1,
|
||||
"salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63"
|
||||
},
|
||||
"mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5"
|
||||
},
|
||||
"id" : "fecfc4ce-e956-48fd-953b-30f8b52ed66c",
|
||||
"version" : 3
|
||||
},
|
||||
"password": "foo",
|
||||
"priv": "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
|
||||
},
|
||||
"30_byte_key": {
|
||||
"json": {
|
||||
"crypto" : {
|
||||
"cipher" : "aes-128-ctr",
|
||||
"cipherparams" : {
|
||||
"iv" : "3ca92af36ad7c2cd92454c59cea5ef00"
|
||||
},
|
||||
"ciphertext" : "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa",
|
||||
"kdf" : "scrypt",
|
||||
"kdfparams" : {
|
||||
"dklen" : 32,
|
||||
"n" : 2,
|
||||
"r" : 8,
|
||||
"p" : 1,
|
||||
"salt" : "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b"
|
||||
},
|
||||
"mac" : "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420"
|
||||
},
|
||||
"id" : "a37e1559-5955-450d-8075-7b8931b392b2",
|
||||
"version" : 3
|
||||
},
|
||||
"password": "foo",
|
||||
"priv": "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"address":"45dea0fb0bba44f4fcf290bba71fd57d7117cbb8","crypto":{"cipher":"aes-128-ctr","ciphertext":"b87781948a1befd247bff51ef4063f716cf6c2d3481163e9a8f42e1f9bb74145","cipherparams":{"iv":"dc4926b48a105133d2f16b96833abf1e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"004244bbdc51cadda545b1cfa43cff9ed2ae88e08c61f1479dbb45410722f8f0"},"mac":"39990c1684557447940d4c69e06b1b82b2aceacb43f284df65c956daf3046b85"},"id":"ce541d8d-c79b-40f8-9f8c-20f59616faba","version":3}
|
||||
@@ -1,108 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin,!ios freebsd linux,!arm64 netbsd solaris
|
||||
|
||||
package keystore
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/rjeczalik/notify"
|
||||
)
|
||||
|
||||
type watcher struct {
|
||||
ac *accountCache
|
||||
starting bool
|
||||
running bool
|
||||
ev chan notify.EventInfo
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
func newWatcher(ac *accountCache) *watcher {
|
||||
return &watcher{
|
||||
ac: ac,
|
||||
ev: make(chan notify.EventInfo, 10),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// starts the watcher loop in the background.
|
||||
// Start a watcher in the background if that's not already in progress.
|
||||
// The caller must hold w.ac.mu.
|
||||
func (w *watcher) start() {
|
||||
if w.starting || w.running {
|
||||
return
|
||||
}
|
||||
w.starting = true
|
||||
go w.loop()
|
||||
}
|
||||
|
||||
func (w *watcher) close() {
|
||||
close(w.quit)
|
||||
}
|
||||
|
||||
func (w *watcher) loop() {
|
||||
defer func() {
|
||||
w.ac.mu.Lock()
|
||||
w.running = false
|
||||
w.starting = false
|
||||
w.ac.mu.Unlock()
|
||||
}()
|
||||
logger := log.New("path", w.ac.keydir)
|
||||
|
||||
if err := notify.Watch(w.ac.keydir, w.ev, notify.All); err != nil {
|
||||
logger.Trace("Failed to watch keystore folder", "err", err)
|
||||
return
|
||||
}
|
||||
defer notify.Stop(w.ev)
|
||||
logger.Trace("Started watching keystore folder")
|
||||
defer logger.Trace("Stopped watching keystore folder")
|
||||
|
||||
w.ac.mu.Lock()
|
||||
w.running = true
|
||||
w.ac.mu.Unlock()
|
||||
|
||||
// Wait for file system events and reload.
|
||||
// When an event occurs, the reload call is delayed a bit so that
|
||||
// multiple events arriving quickly only cause a single reload.
|
||||
var (
|
||||
debounceDuration = 500 * time.Millisecond
|
||||
rescanTriggered = false
|
||||
debounce = time.NewTimer(0)
|
||||
)
|
||||
// Ignore initial trigger
|
||||
if !debounce.Stop() {
|
||||
<-debounce.C
|
||||
}
|
||||
defer debounce.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-w.quit:
|
||||
return
|
||||
case <-w.ev:
|
||||
// Trigger the scan (with delay), if not already triggered
|
||||
if !rescanTriggered {
|
||||
debounce.Reset(debounceDuration)
|
||||
rescanTriggered = true
|
||||
}
|
||||
case <-debounce.C:
|
||||
w.ac.scanAccounts()
|
||||
rescanTriggered = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
|
||||
// This is the fallback implementation of directory watching.
|
||||
// It is used on unsupported platforms.
|
||||
|
||||
package keystore
|
||||
|
||||
type watcher struct{ running bool }
|
||||
|
||||
func newWatcher(*accountCache) *watcher { return new(watcher) }
|
||||
func (*watcher) start() {}
|
||||
func (*watcher) close() {}
|
||||
@@ -1,199 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
// Manager is an overarching account manager that can communicate with various
|
||||
// backends for signing transactions.
|
||||
type Manager struct {
|
||||
backends map[reflect.Type][]Backend // Index of backends currently registered
|
||||
updaters []event.Subscription // Wallet update subscriptions for all backends
|
||||
updates chan WalletEvent // Subscription sink for backend wallet changes
|
||||
wallets []Wallet // Cache of all wallets from all registered backends
|
||||
|
||||
feed event.Feed // Wallet feed notifying of arrivals/departures
|
||||
|
||||
quit chan chan error
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewManager creates a generic account manager to sign transaction via various
|
||||
// supported backends.
|
||||
func NewManager(backends ...Backend) *Manager {
|
||||
// Retrieve the initial list of wallets from the backends and sort by URL
|
||||
var wallets []Wallet
|
||||
for _, backend := range backends {
|
||||
wallets = merge(wallets, backend.Wallets()...)
|
||||
}
|
||||
// Subscribe to wallet notifications from all backends
|
||||
updates := make(chan WalletEvent, 4*len(backends))
|
||||
|
||||
subs := make([]event.Subscription, len(backends))
|
||||
for i, backend := range backends {
|
||||
subs[i] = backend.Subscribe(updates)
|
||||
}
|
||||
// Assemble the account manager and return
|
||||
am := &Manager{
|
||||
backends: make(map[reflect.Type][]Backend),
|
||||
updaters: subs,
|
||||
updates: updates,
|
||||
wallets: wallets,
|
||||
quit: make(chan chan error),
|
||||
}
|
||||
for _, backend := range backends {
|
||||
kind := reflect.TypeOf(backend)
|
||||
am.backends[kind] = append(am.backends[kind], backend)
|
||||
}
|
||||
go am.update()
|
||||
|
||||
return am
|
||||
}
|
||||
|
||||
// Close terminates the account manager's internal notification processes.
|
||||
func (am *Manager) Close() error {
|
||||
errc := make(chan error)
|
||||
am.quit <- errc
|
||||
return <-errc
|
||||
}
|
||||
|
||||
// update is the wallet event loop listening for notifications from the backends
|
||||
// and updating the cache of wallets.
|
||||
func (am *Manager) update() {
|
||||
// Close all subscriptions when the manager terminates
|
||||
defer func() {
|
||||
am.lock.Lock()
|
||||
for _, sub := range am.updaters {
|
||||
sub.Unsubscribe()
|
||||
}
|
||||
am.updaters = nil
|
||||
am.lock.Unlock()
|
||||
}()
|
||||
|
||||
// Loop until termination
|
||||
for {
|
||||
select {
|
||||
case event := <-am.updates:
|
||||
// Wallet event arrived, update local cache
|
||||
am.lock.Lock()
|
||||
switch event.Kind {
|
||||
case WalletArrived:
|
||||
am.wallets = merge(am.wallets, event.Wallet)
|
||||
case WalletDropped:
|
||||
am.wallets = drop(am.wallets, event.Wallet)
|
||||
}
|
||||
am.lock.Unlock()
|
||||
|
||||
// Notify any listeners of the event
|
||||
am.feed.Send(event)
|
||||
|
||||
case errc := <-am.quit:
|
||||
// Manager terminating, return
|
||||
errc <- nil
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backends retrieves the backend(s) with the given type from the account manager.
|
||||
func (am *Manager) Backends(kind reflect.Type) []Backend {
|
||||
return am.backends[kind]
|
||||
}
|
||||
|
||||
// Wallets returns all signer accounts registered under this account manager.
|
||||
func (am *Manager) Wallets() []Wallet {
|
||||
am.lock.RLock()
|
||||
defer am.lock.RUnlock()
|
||||
|
||||
cpy := make([]Wallet, len(am.wallets))
|
||||
copy(cpy, am.wallets)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// Wallet retrieves the wallet associated with a particular URL.
|
||||
func (am *Manager) Wallet(url string) (Wallet, error) {
|
||||
am.lock.RLock()
|
||||
defer am.lock.RUnlock()
|
||||
|
||||
parsed, err := parseURL(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, wallet := range am.Wallets() {
|
||||
if wallet.URL() == parsed {
|
||||
return wallet, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownWallet
|
||||
}
|
||||
|
||||
// Find attempts to locate the wallet corresponding to a specific account. Since
|
||||
// accounts can be dynamically added to and removed from wallets, this method has
|
||||
// a linear runtime in the number of wallets.
|
||||
func (am *Manager) Find(account Account) (Wallet, error) {
|
||||
am.lock.RLock()
|
||||
defer am.lock.RUnlock()
|
||||
|
||||
for _, wallet := range am.wallets {
|
||||
if wallet.Contains(account) {
|
||||
return wallet, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrUnknownAccount
|
||||
}
|
||||
|
||||
// Subscribe creates an async subscription to receive notifications when the
|
||||
// manager detects the arrival or departure of a wallet from any of its backends.
|
||||
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
|
||||
return am.feed.Subscribe(sink)
|
||||
}
|
||||
|
||||
// merge is a sorted analogue of append for wallets, where the ordering of the
|
||||
// origin list is preserved by inserting new wallets at the correct position.
|
||||
//
|
||||
// The original slice is assumed to be already sorted by URL.
|
||||
func merge(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||
for _, wallet := range wallets {
|
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
|
||||
if n == len(slice) {
|
||||
slice = append(slice, wallet)
|
||||
continue
|
||||
}
|
||||
slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// drop is the couterpart of merge, which looks up wallets from within the sorted
|
||||
// cache and removes the ones specified.
|
||||
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||
for _, wallet := range wallets {
|
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
|
||||
if n == len(slice) {
|
||||
// Wallet not found, may happen during startup
|
||||
continue
|
||||
}
|
||||
slice = append(slice[:n], slice[n+1:]...)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
104
accounts/url.go
104
accounts/url.go
@@ -1,104 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// URL represents the canonical identification URL of a wallet or account.
|
||||
//
|
||||
// It is a simplified version of url.URL, with the important limitations (which
|
||||
// are considered features here) that it contains value-copyable components only,
|
||||
// as well as that it doesn't do any URL encoding/decoding of special characters.
|
||||
//
|
||||
// The former is important to allow an account to be copied without leaving live
|
||||
// references to the original version, whereas the latter is important to ensure
|
||||
// one single canonical form opposed to many allowed ones by the RFC 3986 spec.
|
||||
//
|
||||
// As such, these URLs should not be used outside of the scope of an Ethereum
|
||||
// wallet or account.
|
||||
type URL struct {
|
||||
Scheme string // Protocol scheme to identify a capable account backend
|
||||
Path string // Path for the backend to identify a unique entity
|
||||
}
|
||||
|
||||
// parseURL converts a user supplied URL into the accounts specific structure.
|
||||
func parseURL(url string) (URL, error) {
|
||||
parts := strings.Split(url, "://")
|
||||
if len(parts) != 2 || parts[0] == "" {
|
||||
return URL{}, errors.New("protocol scheme missing")
|
||||
}
|
||||
return URL{
|
||||
Scheme: parts[0],
|
||||
Path: parts[1],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (u URL) String() string {
|
||||
if u.Scheme != "" {
|
||||
return fmt.Sprintf("%s://%s", u.Scheme, u.Path)
|
||||
}
|
||||
return u.Path
|
||||
}
|
||||
|
||||
// TerminalString implements the log.TerminalStringer interface.
|
||||
func (u URL) TerminalString() string {
|
||||
url := u.String()
|
||||
if len(url) > 32 {
|
||||
return url[:31] + "…"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaller interface.
|
||||
func (u URL) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(u.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses url.
|
||||
func (u *URL) UnmarshalJSON(input []byte) error {
|
||||
var textUrl string
|
||||
err := json.Unmarshal(input, &textUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url, err := parseURL(textUrl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Scheme = url.Scheme
|
||||
u.Path = url.Path
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cmp compares x and y and returns:
|
||||
//
|
||||
// -1 if x < y
|
||||
// 0 if x == y
|
||||
// +1 if x > y
|
||||
//
|
||||
func (u URL) Cmp(url URL) int {
|
||||
if u.Scheme == url.Scheme {
|
||||
return strings.Compare(u.Path, url.Path)
|
||||
}
|
||||
return strings.Compare(u.Scheme, url.Scheme)
|
||||
}
|
||||
@@ -1,238 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package usbwallet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/karalabe/hid"
|
||||
)
|
||||
|
||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
const LedgerScheme = "ledger"
|
||||
|
||||
// TrezorScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
const TrezorScheme = "trezor"
|
||||
|
||||
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
|
||||
// notifications don't work).
|
||||
const refreshCycle = time.Second
|
||||
|
||||
// refreshThrottling is the minimum time between wallet refreshes to avoid USB
|
||||
// trashing.
|
||||
const refreshThrottling = 500 * time.Millisecond
|
||||
|
||||
// Hub is a accounts.Backend that can find and handle generic USB hardware wallets.
|
||||
type Hub struct {
|
||||
scheme string // Protocol scheme prefixing account and wallet URLs.
|
||||
vendorID uint16 // USB vendor identifier used for device discovery
|
||||
productIDs []uint16 // USB product identifiers used for device discovery
|
||||
usageID uint16 // USB usage page identifier used for macOS device discovery
|
||||
endpointID int // USB endpoint identifier used for non-macOS device discovery
|
||||
makeDriver func(log.Logger) driver // Factory method to construct a vendor specific driver
|
||||
|
||||
refreshed time.Time // Time instance when the list of wallets was last refreshed
|
||||
wallets []accounts.Wallet // List of USB wallet devices currently tracking
|
||||
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||
updating bool // Whether the event notification loop is running
|
||||
|
||||
quit chan chan error
|
||||
|
||||
stateLock sync.RWMutex // Protects the internals of the hub from racey access
|
||||
|
||||
// TODO(karalabe): remove if hotplug lands on Windows
|
||||
commsPend int // Number of operations blocking enumeration
|
||||
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
||||
}
|
||||
|
||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||
func NewLedgerHub() (*Hub, error) {
|
||||
return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, 0xffa0, 0, newLedgerDriver)
|
||||
}
|
||||
|
||||
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
|
||||
func NewTrezorHub() (*Hub, error) {
|
||||
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver)
|
||||
}
|
||||
|
||||
// newHub creates a new hardware wallet manager for generic USB devices.
|
||||
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
|
||||
if !hid.Supported() {
|
||||
return nil, errors.New("unsupported platform")
|
||||
}
|
||||
hub := &Hub{
|
||||
scheme: scheme,
|
||||
vendorID: vendorID,
|
||||
productIDs: productIDs,
|
||||
usageID: usageID,
|
||||
endpointID: endpointID,
|
||||
makeDriver: makeDriver,
|
||||
quit: make(chan chan error),
|
||||
}
|
||||
hub.refreshWallets()
|
||||
return hub, nil
|
||||
}
|
||||
|
||||
// Wallets implements accounts.Backend, returning all the currently tracked USB
|
||||
// devices that appear to be hardware wallets.
|
||||
func (hub *Hub) Wallets() []accounts.Wallet {
|
||||
// Make sure the list of wallets is up to date
|
||||
hub.refreshWallets()
|
||||
|
||||
hub.stateLock.RLock()
|
||||
defer hub.stateLock.RUnlock()
|
||||
|
||||
cpy := make([]accounts.Wallet, len(hub.wallets))
|
||||
copy(cpy, hub.wallets)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// refreshWallets scans the USB devices attached to the machine and updates the
|
||||
// list of wallets based on the found devices.
|
||||
func (hub *Hub) refreshWallets() {
|
||||
// Don't scan the USB like crazy it the user fetches wallets in a loop
|
||||
hub.stateLock.RLock()
|
||||
elapsed := time.Since(hub.refreshed)
|
||||
hub.stateLock.RUnlock()
|
||||
|
||||
if elapsed < refreshThrottling {
|
||||
return
|
||||
}
|
||||
// Retrieve the current list of USB wallet devices
|
||||
var devices []hid.DeviceInfo
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
||||
// breaking the Ledger protocol if that is waiting for user confirmation. This
|
||||
// is a bug acknowledged at Ledger, but it won't be fixed on old devices so we
|
||||
// need to prevent concurrent comms ourselves. The more elegant solution would
|
||||
// be to ditch enumeration in favor of hotplug events, but that don't work yet
|
||||
// on Windows so if we need to hack it anyway, this is more elegant for now.
|
||||
hub.commsLock.Lock()
|
||||
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
||||
hub.commsLock.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, info := range hid.Enumerate(hub.vendorID, 0) {
|
||||
for _, id := range hub.productIDs {
|
||||
if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) {
|
||||
devices = append(devices, info)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
// See rationale before the enumeration why this is needed and only on Linux.
|
||||
hub.commsLock.Unlock()
|
||||
}
|
||||
// Transform the current list of wallets into the new one
|
||||
hub.stateLock.Lock()
|
||||
|
||||
wallets := make([]accounts.Wallet, 0, len(devices))
|
||||
events := []accounts.WalletEvent{}
|
||||
|
||||
for _, device := range devices {
|
||||
url := accounts.URL{Scheme: hub.scheme, Path: device.Path}
|
||||
|
||||
// Drop wallets in front of the next device or those that failed for some reason
|
||||
for len(hub.wallets) > 0 {
|
||||
// Abort if we're past the current device and found an operational one
|
||||
_, failure := hub.wallets[0].Status()
|
||||
if hub.wallets[0].URL().Cmp(url) >= 0 || failure == nil {
|
||||
break
|
||||
}
|
||||
// Drop the stale and failed devices
|
||||
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped})
|
||||
hub.wallets = hub.wallets[1:]
|
||||
}
|
||||
// If there are no more wallets or the device is before the next, wrap new wallet
|
||||
if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 {
|
||||
logger := log.New("url", url)
|
||||
wallet := &wallet{hub: hub, driver: hub.makeDriver(logger), url: &url, info: device, log: logger}
|
||||
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||
wallets = append(wallets, wallet)
|
||||
continue
|
||||
}
|
||||
// If the device is the same as the first wallet, keep it
|
||||
if hub.wallets[0].URL().Cmp(url) == 0 {
|
||||
wallets = append(wallets, hub.wallets[0])
|
||||
hub.wallets = hub.wallets[1:]
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Drop any leftover wallets and set the new batch
|
||||
for _, wallet := range hub.wallets {
|
||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||
}
|
||||
hub.refreshed = time.Now()
|
||||
hub.wallets = wallets
|
||||
hub.stateLock.Unlock()
|
||||
|
||||
// Fire all wallet events and return
|
||||
for _, event := range events {
|
||||
hub.updateFeed.Send(event)
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||
// receive notifications on the addition or removal of USB wallets.
|
||||
func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||
// We need the mutex to reliably start/stop the update loop
|
||||
hub.stateLock.Lock()
|
||||
defer hub.stateLock.Unlock()
|
||||
|
||||
// Subscribe the caller and track the subscriber count
|
||||
sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink))
|
||||
|
||||
// Subscribers require an active notification loop, start it
|
||||
if !hub.updating {
|
||||
hub.updating = true
|
||||
go hub.updater()
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
// updater is responsible for maintaining an up-to-date list of wallets managed
|
||||
// by the USB hub, and for firing wallet addition/removal events.
|
||||
func (hub *Hub) updater() {
|
||||
for {
|
||||
// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout
|
||||
// <-hub.changes
|
||||
time.Sleep(refreshCycle)
|
||||
|
||||
// Run the wallet refresher
|
||||
hub.refreshWallets()
|
||||
|
||||
// If all our subscribers left, stop the updater
|
||||
hub.stateLock.Lock()
|
||||
if hub.updateScope.Count() == 0 {
|
||||
hub.updating = false
|
||||
hub.stateLock.Unlock()
|
||||
return
|
||||
}
|
||||
hub.stateLock.Unlock()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,905 +0,0 @@
|
||||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
|
||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
/**
|
||||
* Messages for TREZOR communication
|
||||
*/
|
||||
|
||||
// Sugar for easier handling in Java
|
||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||
option java_outer_classname = "TrezorMessage";
|
||||
|
||||
import "types.proto";
|
||||
|
||||
/**
|
||||
* Mapping between Trezor wire identifier (uint) and a protobuf message
|
||||
*/
|
||||
enum MessageType {
|
||||
MessageType_Initialize = 0 [(wire_in) = true];
|
||||
MessageType_Ping = 1 [(wire_in) = true];
|
||||
MessageType_Success = 2 [(wire_out) = true];
|
||||
MessageType_Failure = 3 [(wire_out) = true];
|
||||
MessageType_ChangePin = 4 [(wire_in) = true];
|
||||
MessageType_WipeDevice = 5 [(wire_in) = true];
|
||||
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
||||
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true];
|
||||
MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true];
|
||||
MessageType_GetEntropy = 9 [(wire_in) = true];
|
||||
MessageType_Entropy = 10 [(wire_out) = true];
|
||||
MessageType_GetPublicKey = 11 [(wire_in) = true];
|
||||
MessageType_PublicKey = 12 [(wire_out) = true];
|
||||
MessageType_LoadDevice = 13 [(wire_in) = true];
|
||||
MessageType_ResetDevice = 14 [(wire_in) = true];
|
||||
MessageType_SignTx = 15 [(wire_in) = true];
|
||||
MessageType_SimpleSignTx = 16 [(wire_in) = true, deprecated = true];
|
||||
MessageType_Features = 17 [(wire_out) = true];
|
||||
MessageType_PinMatrixRequest = 18 [(wire_out) = true];
|
||||
MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true];
|
||||
MessageType_Cancel = 20 [(wire_in) = true];
|
||||
MessageType_TxRequest = 21 [(wire_out) = true];
|
||||
MessageType_TxAck = 22 [(wire_in) = true];
|
||||
MessageType_CipherKeyValue = 23 [(wire_in) = true];
|
||||
MessageType_ClearSession = 24 [(wire_in) = true];
|
||||
MessageType_ApplySettings = 25 [(wire_in) = true];
|
||||
MessageType_ButtonRequest = 26 [(wire_out) = true];
|
||||
MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true];
|
||||
MessageType_ApplyFlags = 28 [(wire_in) = true];
|
||||
MessageType_GetAddress = 29 [(wire_in) = true];
|
||||
MessageType_Address = 30 [(wire_out) = true];
|
||||
MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true];
|
||||
MessageType_BackupDevice = 34 [(wire_in) = true];
|
||||
MessageType_EntropyRequest = 35 [(wire_out) = true];
|
||||
MessageType_EntropyAck = 36 [(wire_in) = true];
|
||||
MessageType_SignMessage = 38 [(wire_in) = true];
|
||||
MessageType_VerifyMessage = 39 [(wire_in) = true];
|
||||
MessageType_MessageSignature = 40 [(wire_out) = true];
|
||||
MessageType_PassphraseRequest = 41 [(wire_out) = true];
|
||||
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true];
|
||||
MessageType_EstimateTxSize = 43 [(wire_in) = true, deprecated = true];
|
||||
MessageType_TxSize = 44 [(wire_out) = true, deprecated = true];
|
||||
MessageType_RecoveryDevice = 45 [(wire_in) = true];
|
||||
MessageType_WordRequest = 46 [(wire_out) = true];
|
||||
MessageType_WordAck = 47 [(wire_in) = true];
|
||||
MessageType_CipheredKeyValue = 48 [(wire_out) = true];
|
||||
MessageType_EncryptMessage = 49 [(wire_in) = true, deprecated = true];
|
||||
MessageType_EncryptedMessage = 50 [(wire_out) = true, deprecated = true];
|
||||
MessageType_DecryptMessage = 51 [(wire_in) = true, deprecated = true];
|
||||
MessageType_DecryptedMessage = 52 [(wire_out) = true, deprecated = true];
|
||||
MessageType_SignIdentity = 53 [(wire_in) = true];
|
||||
MessageType_SignedIdentity = 54 [(wire_out) = true];
|
||||
MessageType_GetFeatures = 55 [(wire_in) = true];
|
||||
MessageType_EthereumGetAddress = 56 [(wire_in) = true];
|
||||
MessageType_EthereumAddress = 57 [(wire_out) = true];
|
||||
MessageType_EthereumSignTx = 58 [(wire_in) = true];
|
||||
MessageType_EthereumTxRequest = 59 [(wire_out) = true];
|
||||
MessageType_EthereumTxAck = 60 [(wire_in) = true];
|
||||
MessageType_GetECDHSessionKey = 61 [(wire_in) = true];
|
||||
MessageType_ECDHSessionKey = 62 [(wire_out) = true];
|
||||
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
||||
MessageType_EthereumSignMessage = 64 [(wire_in) = true];
|
||||
MessageType_EthereumVerifyMessage = 65 [(wire_in) = true];
|
||||
MessageType_EthereumMessageSignature = 66 [(wire_out) = true];
|
||||
MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true];
|
||||
MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkState = 102 [(wire_debug_out) = true];
|
||||
MessageType_DebugLinkStop = 103 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkLog = 104 [(wire_debug_out) = true];
|
||||
MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true];
|
||||
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
||||
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// Basic messages //
|
||||
////////////////////
|
||||
|
||||
/**
|
||||
* Request: Reset device to default state and ask for device details
|
||||
* @next Features
|
||||
*/
|
||||
message Initialize {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask for device details (no device reset)
|
||||
* @next Features
|
||||
*/
|
||||
message GetFeatures {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Reports various information about the device
|
||||
* @prev Initialize
|
||||
* @prev GetFeatures
|
||||
*/
|
||||
message Features {
|
||||
optional string vendor = 1; // name of the manufacturer, e.g. "bitcointrezor.com"
|
||||
optional uint32 major_version = 2; // major version of the device, e.g. 1
|
||||
optional uint32 minor_version = 3; // minor version of the device, e.g. 0
|
||||
optional uint32 patch_version = 4; // patch version of the device, e.g. 0
|
||||
optional bool bootloader_mode = 5; // is device in bootloader mode?
|
||||
optional string device_id = 6; // device's unique identifier
|
||||
optional bool pin_protection = 7; // is device protected by PIN?
|
||||
optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase?
|
||||
optional string language = 9; // device language
|
||||
optional string label = 10; // device description label
|
||||
repeated CoinType coins = 11; // supported coins
|
||||
optional bool initialized = 12; // does device contain seed?
|
||||
optional bytes revision = 13; // SCM revision of firmware
|
||||
optional bytes bootloader_hash = 14; // hash of the bootloader
|
||||
optional bool imported = 15; // was storage imported from an external source?
|
||||
optional bool pin_cached = 16; // is PIN already cached in session?
|
||||
optional bool passphrase_cached = 17; // is passphrase already cached in session?
|
||||
optional bool firmware_present = 18; // is valid firmware loaded?
|
||||
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
||||
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: clear session (removes cached PIN, passphrase, etc).
|
||||
* @next Success
|
||||
*/
|
||||
message ClearSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: change language and/or label of the device
|
||||
* @next Success
|
||||
* @next Failure
|
||||
* @next ButtonRequest
|
||||
* @next PinMatrixRequest
|
||||
*/
|
||||
message ApplySettings {
|
||||
optional string language = 1;
|
||||
optional string label = 2;
|
||||
optional bool use_passphrase = 3;
|
||||
optional bytes homescreen = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: set flags of the device
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message ApplyFlags {
|
||||
optional uint32 flags = 1; // bitmask, can only set bits, not unset
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Starts workflow for setting/changing/removing the PIN
|
||||
* @next ButtonRequest
|
||||
* @next PinMatrixRequest
|
||||
*/
|
||||
message ChangePin {
|
||||
optional bool remove = 1; // is PIN removal requested?
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Test if the device is alive, device sends back the message in Success response
|
||||
* @next Success
|
||||
*/
|
||||
message Ping {
|
||||
optional string message = 1; // message to send back in Success message
|
||||
optional bool button_protection = 2; // ask for button press
|
||||
optional bool pin_protection = 3; // ask for PIN if set in device
|
||||
optional bool passphrase_protection = 4; // ask for passphrase if set in device
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Success of the previous request
|
||||
*/
|
||||
message Success {
|
||||
optional string message = 1; // human readable description of action or request-specific payload
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Failure of the previous request
|
||||
*/
|
||||
message Failure {
|
||||
optional FailureType code = 1; // computer-readable definition of the error state
|
||||
optional string message = 2; // human-readable message of the error state
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device is waiting for HW button press.
|
||||
* @next ButtonAck
|
||||
* @next Cancel
|
||||
*/
|
||||
message ButtonRequest {
|
||||
optional ButtonRequestType code = 1;
|
||||
optional string data = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer agrees to wait for HW button press
|
||||
* @prev ButtonRequest
|
||||
*/
|
||||
message ButtonAck {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
|
||||
* @next PinMatrixAck
|
||||
* @next Cancel
|
||||
*/
|
||||
message PinMatrixRequest {
|
||||
optional PinMatrixRequestType type = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer responds with encoded PIN
|
||||
* @prev PinMatrixRequest
|
||||
*/
|
||||
message PinMatrixAck {
|
||||
required string pin = 1; // matrix encoded PIN entered by user
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Abort last operation that required user interaction
|
||||
* @prev ButtonRequest
|
||||
* @prev PinMatrixRequest
|
||||
* @prev PassphraseRequest
|
||||
*/
|
||||
message Cancel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device awaits encryption passphrase
|
||||
* @next PassphraseAck
|
||||
* @next Cancel
|
||||
*/
|
||||
message PassphraseRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Send passphrase back
|
||||
* @prev PassphraseRequest
|
||||
*/
|
||||
message PassphraseAck {
|
||||
required string passphrase = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Request a sample of random data generated by hardware RNG. May be used for testing.
|
||||
* @next ButtonRequest
|
||||
* @next Entropy
|
||||
* @next Failure
|
||||
*/
|
||||
message GetEntropy {
|
||||
required uint32 size = 1; // size of requested entropy
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Reply with random data generated by internal RNG
|
||||
* @prev GetEntropy
|
||||
*/
|
||||
message Entropy {
|
||||
required bytes entropy = 1; // stream of random generated bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device for public key corresponding to address_n path
|
||||
* @next PassphraseRequest
|
||||
* @next PublicKey
|
||||
* @next Failure
|
||||
*/
|
||||
message GetPublicKey {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional string ecdsa_curve_name = 2; // ECDSA curve name to use
|
||||
optional bool show_display = 3; // optionally show on display before sending the result
|
||||
optional string coin_name = 4 [default='Bitcoin'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Contains public key derived from device private seed
|
||||
* @prev GetPublicKey
|
||||
*/
|
||||
message PublicKey {
|
||||
required HDNodeType node = 1; // BIP32 public node
|
||||
optional string xpub = 2; // serialized form of public node
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device for address corresponding to address_n path
|
||||
* @next PassphraseRequest
|
||||
* @next Address
|
||||
* @next Failure
|
||||
*/
|
||||
message GetAddress {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional string coin_name = 2 [default='Bitcoin'];
|
||||
optional bool show_display = 3 ; // optionally show on display before sending the result
|
||||
optional MultisigRedeemScriptType multisig = 4; // filled if we are showing a multisig address
|
||||
optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device for Ethereum address corresponding to address_n path
|
||||
* @next PassphraseRequest
|
||||
* @next EthereumAddress
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumGetAddress {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Contains address derived from device private seed
|
||||
* @prev GetAddress
|
||||
*/
|
||||
message Address {
|
||||
required string address = 1; // Coin address in Base58 encoding
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Contains an Ethereum address derived from device private seed
|
||||
* @prev EthereumGetAddress
|
||||
*/
|
||||
message EthereumAddress {
|
||||
required bytes address = 1; // Coin address as an Ethereum 160 bit hash
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Request device to wipe all sensitive data and settings
|
||||
* @next ButtonRequest
|
||||
*/
|
||||
message WipeDevice {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Load seed and related internal settings from the computer
|
||||
* @next ButtonRequest
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message LoadDevice {
|
||||
optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words)
|
||||
optional HDNodeType node = 2; // BIP-32 node
|
||||
optional string pin = 3; // set PIN protection
|
||||
optional bool passphrase_protection = 4; // enable master node encryption using passphrase
|
||||
optional string language = 5 [default='english']; // device language
|
||||
optional string label = 6; // device label
|
||||
optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum
|
||||
optional uint32 u2f_counter = 8; // U2F counter
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to do initialization involving user interaction
|
||||
* @next EntropyRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message ResetDevice {
|
||||
optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy
|
||||
optional uint32 strength = 2 [default=256]; // strength of seed in bits
|
||||
optional bool passphrase_protection = 3; // enable master node encryption using passphrase
|
||||
optional bool pin_protection = 4; // enable PIN protection
|
||||
optional string language = 5 [default='english']; // device language
|
||||
optional string label = 6; // device label
|
||||
optional uint32 u2f_counter = 7; // U2F counter
|
||||
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Perform backup of the device seed if not backed up using ResetDevice
|
||||
* @next ButtonRequest
|
||||
*/
|
||||
message BackupDevice {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Ask for additional entropy from host computer
|
||||
* @prev ResetDevice
|
||||
* @next EntropyAck
|
||||
*/
|
||||
message EntropyRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Provide additional entropy for seed generation function
|
||||
* @prev EntropyRequest
|
||||
* @next ButtonRequest
|
||||
*/
|
||||
message EntropyAck {
|
||||
optional bytes entropy = 1; // 256 bits (32 bytes) of random data
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Start recovery workflow asking user for specific words of mnemonic
|
||||
* Used to recovery device safely even on untrusted computer.
|
||||
* @next WordRequest
|
||||
*/
|
||||
message RecoveryDevice {
|
||||
optional uint32 word_count = 1; // number of words in BIP-39 mnemonic
|
||||
optional bool passphrase_protection = 2; // enable master node encryption using passphrase
|
||||
optional bool pin_protection = 3; // enable PIN protection
|
||||
optional string language = 4 [default='english']; // device language
|
||||
optional string label = 5; // device label
|
||||
optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process
|
||||
// 7 reserved for unused recovery method
|
||||
optional uint32 type = 8; // supported recovery type (see RecoveryType)
|
||||
optional uint32 u2f_counter = 9; // U2F counter
|
||||
optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device is waiting for user to enter word of the mnemonic
|
||||
* Its position is shown only on device's internal display.
|
||||
* @prev RecoveryDevice
|
||||
* @prev WordAck
|
||||
*/
|
||||
message WordRequest {
|
||||
optional WordRequestType type = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer replies with word from the mnemonic
|
||||
* @prev WordRequest
|
||||
* @next WordRequest
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message WordAck {
|
||||
required string word = 1; // one word of mnemonic on asked position
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// Message signing messages //
|
||||
//////////////////////////////
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign message
|
||||
* @next MessageSignature
|
||||
* @next Failure
|
||||
*/
|
||||
message SignMessage {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes message = 2; // message to be signed
|
||||
optional string coin_name = 3 [default='Bitcoin']; // coin to use for signing
|
||||
optional InputScriptType script_type = 4 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to verify message
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message VerifyMessage {
|
||||
optional string address = 1; // address to verify
|
||||
optional bytes signature = 2; // signature to verify
|
||||
optional bytes message = 3; // message to verify
|
||||
optional string coin_name = 4 [default='Bitcoin']; // coin to use for verifying
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Signed message
|
||||
* @prev SignMessage
|
||||
*/
|
||||
message MessageSignature {
|
||||
optional string address = 1; // address used to sign the message
|
||||
optional bytes signature = 2; // signature of the message
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// Encryption/decryption //
|
||||
///////////////////////////
|
||||
|
||||
/**
|
||||
* Request: Ask device to encrypt message
|
||||
* @next EncryptedMessage
|
||||
* @next Failure
|
||||
*/
|
||||
message EncryptMessage {
|
||||
optional bytes pubkey = 1; // public key
|
||||
optional bytes message = 2; // message to encrypt
|
||||
optional bool display_only = 3; // show just on display? (don't send back via wire)
|
||||
repeated uint32 address_n = 4; // BIP-32 path to derive the signing key from master node
|
||||
optional string coin_name = 5 [default='Bitcoin']; // coin to use for signing
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Encrypted message
|
||||
* @prev EncryptMessage
|
||||
*/
|
||||
message EncryptedMessage {
|
||||
optional bytes nonce = 1; // nonce used during encryption
|
||||
optional bytes message = 2; // encrypted message
|
||||
optional bytes hmac = 3; // message hmac
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to decrypt message
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message DecryptMessage {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the decryption key from master node
|
||||
optional bytes nonce = 2; // nonce used during encryption
|
||||
optional bytes message = 3; // message to decrypt
|
||||
optional bytes hmac = 4; // message hmac
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Decrypted message
|
||||
* @prev DecryptedMessage
|
||||
*/
|
||||
message DecryptedMessage {
|
||||
optional bytes message = 1; // decrypted message
|
||||
optional string address = 2; // address used to sign the message (if used)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to encrypt or decrypt value of given key
|
||||
* @next CipheredKeyValue
|
||||
* @next Failure
|
||||
*/
|
||||
message CipherKeyValue {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional string key = 2; // key component of key:value
|
||||
optional bytes value = 3; // value component of key:value
|
||||
optional bool encrypt = 4; // are we encrypting (True) or decrypting (False)?
|
||||
optional bool ask_on_encrypt = 5; // should we ask on encrypt operation?
|
||||
optional bool ask_on_decrypt = 6; // should we ask on decrypt operation?
|
||||
optional bytes iv = 7; // initialization vector (will be computed if not set)
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Return ciphered/deciphered value
|
||||
* @prev CipherKeyValue
|
||||
*/
|
||||
message CipheredKeyValue {
|
||||
optional bytes value = 1; // ciphered/deciphered value
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Transaction signing messages //
|
||||
//////////////////////////////////
|
||||
|
||||
/**
|
||||
* Request: Estimated size of the transaction
|
||||
* This behaves exactly like SignTx, which means that it can ask using TxRequest
|
||||
* This call is non-blocking (except possible PassphraseRequest to unlock the seed)
|
||||
* @next TxSize
|
||||
* @next Failure
|
||||
*/
|
||||
message EstimateTxSize {
|
||||
required uint32 outputs_count = 1; // number of transaction outputs
|
||||
required uint32 inputs_count = 2; // number of transaction inputs
|
||||
optional string coin_name = 3 [default='Bitcoin']; // coin to use
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Estimated size of the transaction
|
||||
* @prev EstimateTxSize
|
||||
*/
|
||||
message TxSize {
|
||||
optional uint32 tx_size = 1; // estimated size of transaction in bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign transaction
|
||||
* @next PassphraseRequest
|
||||
* @next PinMatrixRequest
|
||||
* @next TxRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message SignTx {
|
||||
required uint32 outputs_count = 1; // number of transaction outputs
|
||||
required uint32 inputs_count = 2; // number of transaction inputs
|
||||
optional string coin_name = 3 [default='Bitcoin']; // coin to use
|
||||
optional uint32 version = 4 [default=1]; // transaction version
|
||||
optional uint32 lock_time = 5 [default=0]; // transaction lock_time
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Simplified transaction signing
|
||||
* This method doesn't support streaming, so there are hardware limits in number of inputs and outputs.
|
||||
* In case of success, the result is returned using TxRequest message.
|
||||
* @next PassphraseRequest
|
||||
* @next PinMatrixRequest
|
||||
* @next TxRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message SimpleSignTx {
|
||||
repeated TxInputType inputs = 1; // transaction inputs
|
||||
repeated TxOutputType outputs = 2; // transaction outputs
|
||||
repeated TransactionType transactions = 3; // transactions whose outputs are used to build current inputs
|
||||
optional string coin_name = 4 [default='Bitcoin']; // coin to use
|
||||
optional uint32 version = 5 [default=1]; // transaction version
|
||||
optional uint32 lock_time = 6 [default=0]; // transaction lock_time
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device asks for information for signing transaction or returns the last result
|
||||
* If request_index is set, device awaits TxAck message (with fields filled in according to request_type)
|
||||
* If signature_index is set, 'signature' contains signed input of signature_index's input
|
||||
* @prev SignTx
|
||||
* @prev SimpleSignTx
|
||||
* @prev TxAck
|
||||
*/
|
||||
message TxRequest {
|
||||
optional RequestType request_type = 1; // what should be filled in TxAck message?
|
||||
optional TxRequestDetailsType details = 2; // request for tx details
|
||||
optional TxRequestSerializedType serialized = 3; // serialized data and request for next
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Reported transaction data
|
||||
* @prev TxRequest
|
||||
* @next TxRequest
|
||||
*/
|
||||
message TxAck {
|
||||
optional TransactionType tx = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign transaction
|
||||
* All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
||||
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||
* @next PassphraseRequest
|
||||
* @next PinMatrixRequest
|
||||
* @next EthereumTxRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignTx {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bytes nonce = 2; // <=256 bit unsigned big endian
|
||||
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes gas_limit = 4; // <=256 bit unsigned big endian
|
||||
optional bytes to = 5; // 160 bit address hash
|
||||
optional bytes value = 6; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes)
|
||||
optional uint32 data_length = 8; // Length of transaction payload
|
||||
optional uint32 chain_id = 9; // Chain Id for EIP 155
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device asks for more data from transaction payload, or returns the signature.
|
||||
* If data_length is set, device awaits that many more bytes of payload.
|
||||
* Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
|
||||
* @prev EthereumSignTx
|
||||
* @next EthereumTxAck
|
||||
*/
|
||||
message EthereumTxRequest {
|
||||
optional uint32 data_length = 1; // Number of bytes being requested (<= 1024)
|
||||
optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28)
|
||||
optional bytes signature_r = 3; // Computed signature R component (256 bit)
|
||||
optional bytes signature_s = 4; // Computed signature S component (256 bit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Transaction payload data.
|
||||
* @prev EthereumTxRequest
|
||||
* @next EthereumTxRequest
|
||||
*/
|
||||
message EthereumTxAck {
|
||||
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// Ethereum: Message signing messages //
|
||||
////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign message
|
||||
* @next EthereumMessageSignature
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignMessage {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes message = 2; // message to be signed
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to verify message
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumVerifyMessage {
|
||||
optional bytes address = 1; // address to verify
|
||||
optional bytes signature = 2; // signature to verify
|
||||
optional bytes message = 3; // message to verify
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Signed message
|
||||
* @prev EthereumSignMessage
|
||||
*/
|
||||
message EthereumMessageSignature {
|
||||
optional bytes address = 1; // address used to sign the message
|
||||
optional bytes signature = 2; // signature of the message
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
// Identity messages //
|
||||
///////////////////////
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign identity
|
||||
* @next SignedIdentity
|
||||
* @next Failure
|
||||
*/
|
||||
message SignIdentity {
|
||||
optional IdentityType identity = 1; // identity
|
||||
optional bytes challenge_hidden = 2; // non-visible challenge
|
||||
optional string challenge_visual = 3; // challenge shown on display (e.g. date+time)
|
||||
optional string ecdsa_curve_name = 4; // ECDSA curve name to use
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device provides signed identity
|
||||
* @prev SignIdentity
|
||||
*/
|
||||
message SignedIdentity {
|
||||
optional string address = 1; // identity address
|
||||
optional bytes public_key = 2; // identity public key
|
||||
optional bytes signature = 3; // signature of the identity data
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// ECDH messages //
|
||||
///////////////////
|
||||
|
||||
/**
|
||||
* Request: Ask device to generate ECDH session key
|
||||
* @next ECDHSessionKey
|
||||
* @next Failure
|
||||
*/
|
||||
message GetECDHSessionKey {
|
||||
optional IdentityType identity = 1; // identity
|
||||
optional bytes peer_public_key = 2; // peer's public key
|
||||
optional string ecdsa_curve_name = 3; // ECDSA curve name to use
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device provides ECDH session key
|
||||
* @prev GetECDHSessionKey
|
||||
*/
|
||||
message ECDHSessionKey {
|
||||
optional bytes session_key = 1; // ECDH session key
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// U2F messages //
|
||||
///////////////////
|
||||
|
||||
/**
|
||||
* Request: Set U2F counter
|
||||
* @next Success
|
||||
*/
|
||||
message SetU2FCounter {
|
||||
optional uint32 u2f_counter = 1; // counter
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Bootloader messages //
|
||||
/////////////////////////
|
||||
|
||||
/**
|
||||
* Request: Ask device to erase its firmware (so it can be replaced via FirmwareUpload)
|
||||
* @next Success
|
||||
* @next FirmwareRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message FirmwareErase {
|
||||
optional uint32 length = 1; // length of new firmware
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Ask for firmware chunk
|
||||
* @next FirmwareUpload
|
||||
*/
|
||||
message FirmwareRequest {
|
||||
optional uint32 offset = 1; // offset of requested firmware chunk
|
||||
optional uint32 length = 2; // length of requested firmware chunk
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Send firmware in binary form to the device
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message FirmwareUpload {
|
||||
required bytes payload = 1; // firmware to be loaded into device
|
||||
optional bytes hash = 2; // hash of the payload
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Request: Perform a device self-test
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message SelfTest {
|
||||
optional bytes payload = 1; // payload to be used in self-test
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Debug messages (only available if DebugLink is enabled) //
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Request: "Press" the button on the device
|
||||
* @next Success
|
||||
*/
|
||||
message DebugLinkDecision {
|
||||
required bool yes_no = 1; // true for "Confirm", false for "Cancel"
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer asks for device state
|
||||
* @next DebugLinkState
|
||||
*/
|
||||
message DebugLinkGetState {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device current state
|
||||
* @prev DebugLinkGetState
|
||||
*/
|
||||
message DebugLinkState {
|
||||
optional bytes layout = 1; // raw buffer of display
|
||||
optional string pin = 2; // current PIN, blank if PIN is not set/enabled
|
||||
optional string matrix = 3; // current PIN matrix
|
||||
optional string mnemonic = 4; // current BIP-39 mnemonic
|
||||
optional HDNodeType node = 5; // current BIP-32 node
|
||||
optional bool passphrase_protection = 6; // is node/mnemonic encrypted using passphrase?
|
||||
optional string reset_word = 7; // word on device display during ResetDevice workflow
|
||||
optional bytes reset_entropy = 8; // current entropy during ResetDevice workflow
|
||||
optional string recovery_fake_word = 9; // (fake) word on display during RecoveryDevice workflow
|
||||
optional uint32 recovery_word_pos = 10; // index of mnemonic word the device is expecting during RecoveryDevice workflow
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to restart
|
||||
*/
|
||||
message DebugLinkStop {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device wants host to log event
|
||||
*/
|
||||
message DebugLinkLog {
|
||||
optional uint32 level = 1;
|
||||
optional string bucket = 2;
|
||||
optional string text = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Read memory from device
|
||||
* @next DebugLinkMemory
|
||||
*/
|
||||
message DebugLinkMemoryRead {
|
||||
optional uint32 address = 1;
|
||||
optional uint32 length = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device sends memory back
|
||||
* @prev DebugLinkMemoryRead
|
||||
*/
|
||||
message DebugLinkMemory {
|
||||
optional bytes memory = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Write memory to device.
|
||||
* WARNING: Writing to the wrong location can irreparably break the device.
|
||||
*/
|
||||
message DebugLinkMemoryWrite {
|
||||
optional uint32 address = 1;
|
||||
optional bytes memory = 2;
|
||||
optional bool flash = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Erase block of flash on device
|
||||
* WARNING: Writing to the wrong location can irreparably break the device.
|
||||
*/
|
||||
message DebugLinkFlashErase {
|
||||
optional uint32 sector = 1;
|
||||
}
|
||||
@@ -1,46 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Trezor hardware
|
||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
||||
|
||||
//go:generate protoc --go_out=import_path=trezor:. types.proto messages.proto
|
||||
|
||||
// Package trezor contains the wire protocol wrapper in Go.
|
||||
package trezor
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// Type returns the protocol buffer type number of a specific message. If the
|
||||
// message is nil, this method panics!
|
||||
func Type(msg proto.Message) uint16 {
|
||||
return uint16(MessageType_value["MessageType_"+reflect.TypeOf(msg).Elem().Name()])
|
||||
}
|
||||
|
||||
// Name returns the friendly message type name of a specific protocol buffer
|
||||
// type number.
|
||||
func Name(kind uint16) string {
|
||||
name := MessageType_name[int32(kind)]
|
||||
if len(name) < 12 {
|
||||
return name
|
||||
}
|
||||
return name[12:]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,278 +0,0 @@
|
||||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/types.proto
|
||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
/**
|
||||
* Types for TREZOR communication
|
||||
*
|
||||
* @author Marek Palatinus <slush@satoshilabs.com>
|
||||
* @version 1.2
|
||||
*/
|
||||
|
||||
// Sugar for easier handling in Java
|
||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||
option java_outer_classname = "TrezorType";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
/**
|
||||
* Options for specifying message direction and type of wire (normal/debug)
|
||||
*/
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR
|
||||
optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC
|
||||
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR
|
||||
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC
|
||||
optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode
|
||||
optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of failures returned by Failure message
|
||||
* @used_in Failure
|
||||
*/
|
||||
enum FailureType {
|
||||
Failure_UnexpectedMessage = 1;
|
||||
Failure_ButtonExpected = 2;
|
||||
Failure_DataError = 3;
|
||||
Failure_ActionCancelled = 4;
|
||||
Failure_PinExpected = 5;
|
||||
Failure_PinCancelled = 6;
|
||||
Failure_PinInvalid = 7;
|
||||
Failure_InvalidSignature = 8;
|
||||
Failure_ProcessError = 9;
|
||||
Failure_NotEnoughFunds = 10;
|
||||
Failure_NotInitialized = 11;
|
||||
Failure_FirmwareError = 99;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of script which will be used for transaction output
|
||||
* @used_in TxOutputType
|
||||
*/
|
||||
enum OutputScriptType {
|
||||
PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness)
|
||||
PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS)
|
||||
PAYTOMULTISIG = 2; // only for change output
|
||||
PAYTOOPRETURN = 3; // op_return
|
||||
PAYTOWITNESS = 4; // only for change output
|
||||
PAYTOP2SHWITNESS = 5; // only for change output
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of script which will be used for transaction output
|
||||
* @used_in TxInputType
|
||||
*/
|
||||
enum InputScriptType {
|
||||
SPENDADDRESS = 0; // standard p2pkh address
|
||||
SPENDMULTISIG = 1; // p2sh multisig address
|
||||
EXTERNAL = 2; // reserved for external inputs (coinjoin)
|
||||
SPENDWITNESS = 3; // native segwit
|
||||
SPENDP2SHWITNESS = 4; // segwit over p2sh (backward compatible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of information required by transaction signing process
|
||||
* @used_in TxRequest
|
||||
*/
|
||||
enum RequestType {
|
||||
TXINPUT = 0;
|
||||
TXOUTPUT = 1;
|
||||
TXMETA = 2;
|
||||
TXFINISHED = 3;
|
||||
TXEXTRADATA = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of button request
|
||||
* @used_in ButtonRequest
|
||||
*/
|
||||
enum ButtonRequestType {
|
||||
ButtonRequest_Other = 1;
|
||||
ButtonRequest_FeeOverThreshold = 2;
|
||||
ButtonRequest_ConfirmOutput = 3;
|
||||
ButtonRequest_ResetDevice = 4;
|
||||
ButtonRequest_ConfirmWord = 5;
|
||||
ButtonRequest_WipeDevice = 6;
|
||||
ButtonRequest_ProtectCall = 7;
|
||||
ButtonRequest_SignTx = 8;
|
||||
ButtonRequest_FirmwareCheck = 9;
|
||||
ButtonRequest_Address = 10;
|
||||
ButtonRequest_PublicKey = 11;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of PIN request
|
||||
* @used_in PinMatrixRequest
|
||||
*/
|
||||
enum PinMatrixRequestType {
|
||||
PinMatrixRequestType_Current = 1;
|
||||
PinMatrixRequestType_NewFirst = 2;
|
||||
PinMatrixRequestType_NewSecond = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of recovery procedure. These should be used as bitmask, e.g.,
|
||||
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
|
||||
* listing every method supported by the host computer.
|
||||
*
|
||||
* Note that ScrambledWords must be supported by every implementation
|
||||
* for backward compatibility; there is no way to not support it.
|
||||
*
|
||||
* @used_in RecoveryDevice
|
||||
*/
|
||||
enum RecoveryDeviceType {
|
||||
// use powers of two when extending this field
|
||||
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
|
||||
RecoveryDeviceType_Matrix = 1; // matrix recovery type
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of Recovery Word request
|
||||
* @used_in WordRequest
|
||||
*/
|
||||
enum WordRequestType {
|
||||
WordRequestType_Plain = 0;
|
||||
WordRequestType_Matrix9 = 1;
|
||||
WordRequestType_Matrix6 = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing BIP32 (hierarchical deterministic) node
|
||||
* Used for imports of private key into the device and exporting public key out of device
|
||||
* @used_in PublicKey
|
||||
* @used_in LoadDevice
|
||||
* @used_in DebugLinkState
|
||||
* @used_in Storage
|
||||
*/
|
||||
message HDNodeType {
|
||||
required uint32 depth = 1;
|
||||
required uint32 fingerprint = 2;
|
||||
required uint32 child_num = 3;
|
||||
required bytes chain_code = 4;
|
||||
optional bytes private_key = 5;
|
||||
optional bytes public_key = 6;
|
||||
}
|
||||
|
||||
message HDNodePathType {
|
||||
required HDNodeType node = 1; // BIP-32 node in deserialized form
|
||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from node
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing Coin
|
||||
* @used_in Features
|
||||
*/
|
||||
message CoinType {
|
||||
optional string coin_name = 1;
|
||||
optional string coin_shortcut = 2;
|
||||
optional uint32 address_type = 3 [default=0];
|
||||
optional uint64 maxfee_kb = 4;
|
||||
optional uint32 address_type_p2sh = 5 [default=5];
|
||||
optional string signed_message_header = 8;
|
||||
optional uint32 xpub_magic = 9 [default=76067358]; // default=0x0488b21e
|
||||
optional uint32 xprv_magic = 10 [default=76066276]; // default=0x0488ade4
|
||||
optional bool segwit = 11;
|
||||
optional uint32 forkid = 12;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of redeem script used in input
|
||||
* @used_in TxInputType
|
||||
*/
|
||||
message MultisigRedeemScriptType {
|
||||
repeated HDNodePathType pubkeys = 1; // pubkeys from multisig address (sorted lexicographically)
|
||||
repeated bytes signatures = 2; // existing signatures for partially signed input
|
||||
optional uint32 m = 3; // "m" from n, how many valid signatures is necessary for spending
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing transaction input
|
||||
* @used_in SimpleSignTx
|
||||
* @used_in TransactionType
|
||||
*/
|
||||
message TxInputType {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes prev_hash = 2; // hash of previous transaction output to spend by this input
|
||||
required uint32 prev_index = 3; // index of previous output to spend
|
||||
optional bytes script_sig = 4; // script signature, unset for tx to sign
|
||||
optional uint32 sequence = 5 [default=4294967295]; // sequence (default=0xffffffff)
|
||||
optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script
|
||||
optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx
|
||||
optional uint64 amount = 8; // amount of previous transaction output (for segwit only)
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing transaction output
|
||||
* @used_in SimpleSignTx
|
||||
* @used_in TransactionType
|
||||
*/
|
||||
message TxOutputType {
|
||||
optional string address = 1; // target coin address in Base58 encoding
|
||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address"
|
||||
required uint64 amount = 3; // amount to spend in satoshis
|
||||
required OutputScriptType script_type = 4; // output script type
|
||||
optional MultisigRedeemScriptType multisig = 5; // defines multisig address; script_type must be PAYTOMULTISIG
|
||||
optional bytes op_return_data = 6; // defines op_return data; script_type must be PAYTOOPRETURN, amount must be 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing compiled transaction output
|
||||
* @used_in TransactionType
|
||||
*/
|
||||
message TxOutputBinType {
|
||||
required uint64 amount = 1;
|
||||
required bytes script_pubkey = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing transaction
|
||||
* @used_in SimpleSignTx
|
||||
*/
|
||||
message TransactionType {
|
||||
optional uint32 version = 1;
|
||||
repeated TxInputType inputs = 2;
|
||||
repeated TxOutputBinType bin_outputs = 3;
|
||||
repeated TxOutputType outputs = 5;
|
||||
optional uint32 lock_time = 4;
|
||||
optional uint32 inputs_cnt = 6;
|
||||
optional uint32 outputs_cnt = 7;
|
||||
optional bytes extra_data = 8;
|
||||
optional uint32 extra_data_len = 9;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing request details
|
||||
* @used_in TxRequest
|
||||
*/
|
||||
message TxRequestDetailsType {
|
||||
optional uint32 request_index = 1; // device expects TxAck message from the computer
|
||||
optional bytes tx_hash = 2; // tx_hash of requested transaction
|
||||
optional uint32 extra_data_len = 3; // length of requested extra data
|
||||
optional uint32 extra_data_offset = 4; // offset of requested extra data
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing serialized data
|
||||
* @used_in TxRequest
|
||||
*/
|
||||
message TxRequestSerializedType {
|
||||
optional uint32 signature_index = 1; // 'signature' field contains signed input of this index
|
||||
optional bytes signature = 2; // signature of the signature_index input
|
||||
optional bytes serialized_tx = 3; // part of serialized and signed transaction
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing identity data
|
||||
* @used_in IdentityType
|
||||
*/
|
||||
message IdentityType {
|
||||
optional string proto = 1; // proto part of URI
|
||||
optional string user = 2; // user part of URI
|
||||
optional string host = 3; // host part of URI
|
||||
optional string port = 4; // port part of URI
|
||||
optional string path = 5; // path part of URI
|
||||
optional uint32 index = 6 [default=0]; // identity index
|
||||
}
|
||||
@@ -1,462 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Ledger hardware
|
||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
||||
|
||||
package usbwallet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"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/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// ledgerOpcode is an enumeration encoding the supported Ledger opcodes.
|
||||
type ledgerOpcode byte
|
||||
|
||||
// ledgerParam1 is an enumeration encoding the supported Ledger parameters for
|
||||
// specific opcodes. The same parameter values may be reused between opcodes.
|
||||
type ledgerParam1 byte
|
||||
|
||||
// ledgerParam2 is an enumeration encoding the supported Ledger parameters for
|
||||
// specific opcodes. The same parameter values may be reused between opcodes.
|
||||
type ledgerParam2 byte
|
||||
|
||||
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
|
||||
|
||||
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
||||
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
|
||||
)
|
||||
|
||||
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
||||
// if the device replies with a mismatching header. This usually means the device
|
||||
// is in browser mode.
|
||||
var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header")
|
||||
|
||||
// errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval
|
||||
// when a response does arrive, but it does not contain the expected data.
|
||||
var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply")
|
||||
|
||||
// ledgerDriver implements the communication with a Ledger hardware wallet.
|
||||
type ledgerDriver struct {
|
||||
device io.ReadWriter // USB device connection to communicate through
|
||||
version [3]byte // Current version of the Ledger firmware (zero if app is offline)
|
||||
browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch)
|
||||
failure error // Any failure that would make the device unusable
|
||||
log log.Logger // Contextual logger to tag the ledger with its id
|
||||
}
|
||||
|
||||
// newLedgerDriver creates a new instance of a Ledger USB protocol driver.
|
||||
func newLedgerDriver(logger log.Logger) driver {
|
||||
return &ledgerDriver{
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Status implements usbwallet.driver, returning various states the Ledger can
|
||||
// currently be in.
|
||||
func (w *ledgerDriver) Status() (string, error) {
|
||||
if w.failure != nil {
|
||||
return fmt.Sprintf("Failed: %v", w.failure), w.failure
|
||||
}
|
||||
if w.browser {
|
||||
return "Ethereum app in browser mode", w.failure
|
||||
}
|
||||
if w.offline() {
|
||||
return "Ethereum app offline", w.failure
|
||||
}
|
||||
return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure
|
||||
}
|
||||
|
||||
// offline returns whether the wallet and the Ethereum app is offline or not.
|
||||
//
|
||||
// The method assumes that the state lock is held!
|
||||
func (w *ledgerDriver) offline() bool {
|
||||
return w.version == [3]byte{0, 0, 0}
|
||||
}
|
||||
|
||||
// Open implements usbwallet.driver, attempting to initialize the connection to the
|
||||
// Ledger hardware wallet. The Ledger does not require a user passphrase, so that
|
||||
// parameter is silently discarded.
|
||||
func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||
w.device, w.failure = device, nil
|
||||
|
||||
_, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath)
|
||||
if err != nil {
|
||||
// Ethereum app is not running or in browser mode, nothing more to do, return
|
||||
if err == errLedgerReplyInvalidHeader {
|
||||
w.browser = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Try to resolve the Ethereum app's version, will fail prior to v1.0.2
|
||||
if w.version, err = w.ledgerVersion(); err != nil {
|
||||
w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements usbwallet.driver, cleaning up and metadata maintained within
|
||||
// the Ledger driver.
|
||||
func (w *ledgerDriver) Close() error {
|
||||
w.browser, w.version = false, [3]byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Heartbeat implements usbwallet.driver, performing a sanity check against the
|
||||
// Ledger to see if it's still online.
|
||||
func (w *ledgerDriver) Heartbeat() error {
|
||||
if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply {
|
||||
w.failure = err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Derive implements usbwallet.driver, sending a derivation request to the Ledger
|
||||
// and returning the Ethereum address located on that derivation path.
|
||||
func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
|
||||
return w.ledgerDerive(path)
|
||||
}
|
||||
|
||||
// SignTx implements usbwallet.driver, sending the transaction to the Ledger and
|
||||
// waiting for the user to confirm or deny the transaction.
|
||||
//
|
||||
// Note, if the version of the Ethereum application running on the Ledger wallet is
|
||||
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
|
||||
// will be returned opposed to silently signing in Homestead mode.
|
||||
func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||
// If the Ethereum app doesn't run, abort
|
||||
if w.offline() {
|
||||
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[1] <= 0 && w.version[2] <= 2 {
|
||||
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
||||
}
|
||||
// All infos gathered and metadata checks out, request signing
|
||||
return w.ledgerSign(path, tx, chainID)
|
||||
}
|
||||
|
||||
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
||||
// on the Ledger wallet.
|
||||
//
|
||||
// The version retrieval protocol is defined as follows:
|
||||
//
|
||||
// CLA | INS | P1 | P2 | Lc | Le
|
||||
// ----+-----+----+----+----+---
|
||||
// E0 | 06 | 00 | 00 | 00 | 04
|
||||
//
|
||||
// With no input data, and the output data being:
|
||||
//
|
||||
// Description | Length
|
||||
// ---------------------------------------------------+--------
|
||||
// Flags 01: arbitrary data signature enabled by user | 1 byte
|
||||
// Application major version | 1 byte
|
||||
// Application minor version | 1 byte
|
||||
// Application patch version | 1 byte
|
||||
func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
|
||||
// Send the request and wait for the response
|
||||
reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil)
|
||||
if err != nil {
|
||||
return [3]byte{}, err
|
||||
}
|
||||
if len(reply) != 4 {
|
||||
return [3]byte{}, errLedgerInvalidVersionReply
|
||||
}
|
||||
// Cache the version for future reference
|
||||
var version [3]byte
|
||||
copy(version[:], reply[1:])
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// ledgerDerive retrieves the currently active Ethereum address from a Ledger
|
||||
// wallet at the specified derivation path.
|
||||
//
|
||||
// The address derivation protocol is defined as follows:
|
||||
//
|
||||
// CLA | INS | P1 | P2 | Lc | Le
|
||||
// ----+-----+----+----+-----+---
|
||||
// E0 | 02 | 00 return address
|
||||
// 01 display address and confirm before returning
|
||||
// | 00: do not return the chain code
|
||||
// | 01: return the chain code
|
||||
// | var | 00
|
||||
//
|
||||
// Where the input data 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
|
||||
//
|
||||
// And the output data is:
|
||||
//
|
||||
// Description | Length
|
||||
// ------------------------+-------------------
|
||||
// Public Key length | 1 byte
|
||||
// Uncompressed Public Key | arbitrary
|
||||
// Ethereum address length | 1 byte
|
||||
// Ethereum address | 40 bytes hex ascii
|
||||
// Chain code if requested | 32 bytes
|
||||
func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, 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)
|
||||
}
|
||||
// Send the request and wait for the response
|
||||
reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
// Discard the public key, we don't need that for now
|
||||
if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
|
||||
return common.Address{}, errors.New("reply lacks public key entry")
|
||||
}
|
||||
reply = reply[1+int(reply[0]):]
|
||||
|
||||
// Extract the Ethereum hex address string
|
||||
if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
|
||||
return common.Address{}, errors.New("reply lacks address entry")
|
||||
}
|
||||
hexstr := reply[1 : 1+int(reply[0])]
|
||||
|
||||
// Decode the hex sting into an Ethereum address and return
|
||||
var address common.Address
|
||||
hex.Decode(address[:], hexstr)
|
||||
return address, nil
|
||||
}
|
||||
|
||||
// ledgerSign sends the transaction to the Ledger wallet, and waits for the user
|
||||
// to confirm or deny the transaction.
|
||||
//
|
||||
// The transaction signing protocol is defined as follows:
|
||||
//
|
||||
// CLA | INS | P1 | P2 | Lc | Le
|
||||
// ----+-----+----+----+-----+---
|
||||
// E0 | 04 | 00: first transaction data block
|
||||
// 80: subsequent transaction data block
|
||||
// | 00 | variable | variable
|
||||
//
|
||||
// Where the input for the first transaction block (first 255 bytes) 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
|
||||
// RLP transaction chunk | arbitrary
|
||||
//
|
||||
// And the input for subsequent transaction blocks (first 255 bytes) are:
|
||||
//
|
||||
// Description | Length
|
||||
// ----------------------+----------
|
||||
// RLP transaction chunk | arbitrary
|
||||
//
|
||||
// And the output data is:
|
||||
//
|
||||
// Description | Length
|
||||
// ------------+---------
|
||||
// signature V | 1 byte
|
||||
// signature R | 32 bytes
|
||||
// signature S | 32 bytes
|
||||
func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, 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 transaction RLP based on whether legacy or EIP155 signing was requested
|
||||
var (
|
||||
txrlp []byte
|
||||
err error
|
||||
)
|
||||
if chainID == nil {
|
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
} else {
|
||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
}
|
||||
payload := append(path, txrlp...)
|
||||
|
||||
// Send the request and wait for the response
|
||||
var (
|
||||
op = ledgerP1InitTransactionData
|
||||
reply []byte
|
||||
)
|
||||
for len(payload) > 0 {
|
||||
// Calculate the size of the next data chunk
|
||||
chunk := 255
|
||||
if chunk > len(payload) {
|
||||
chunk = len(payload)
|
||||
}
|
||||
// Send the chunk over, ensuring it's processed correctly
|
||||
reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk])
|
||||
if err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
// Shift the payload and ensure subsequent chunks are marked as such
|
||||
payload = payload[chunk:]
|
||||
op = ledgerP1ContTransactionData
|
||||
}
|
||||
// Extract the Ethereum signature and do a sanity validation
|
||||
if len(reply) != 65 {
|
||||
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||
}
|
||||
signature := append(reply[1:], reply[0])
|
||||
|
||||
// Create the correct signer and signature transform based on the chain ID
|
||||
var signer types.Signer
|
||||
if chainID == nil {
|
||||
signer = new(types.HomesteadSigner)
|
||||
} else {
|
||||
signer = types.NewEIP155Signer(chainID)
|
||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
||||
}
|
||||
signed, err := tx.WithSignature(signer, signature)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
sender, err := types.Sender(signer, signed)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
return sender, signed, nil
|
||||
}
|
||||
|
||||
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
||||
// message and retrieving the response.
|
||||
//
|
||||
// The common transport header is defined as follows:
|
||||
//
|
||||
// Description | Length
|
||||
// --------------------------------------+----------
|
||||
// Communication channel ID (big endian) | 2 bytes
|
||||
// Command tag | 1 byte
|
||||
// Packet sequence index (big endian) | 2 bytes
|
||||
// Payload | arbitrary
|
||||
//
|
||||
// The Communication channel ID allows commands multiplexing over the same
|
||||
// physical link. It is not used for the time being, and should be set to 0101
|
||||
// to avoid compatibility issues with implementations ignoring a leading 00 byte.
|
||||
//
|
||||
// The Command tag describes the message content. Use TAG_APDU (0x05) for standard
|
||||
// APDU payloads, or TAG_PING (0x02) for a simple link test.
|
||||
//
|
||||
// The Packet sequence index describes the current sequence for fragmented payloads.
|
||||
// The first fragment index is 0x00.
|
||||
//
|
||||
// APDU Command payloads are encoded as follows:
|
||||
//
|
||||
// Description | Length
|
||||
// -----------------------------------
|
||||
// APDU length (big endian) | 2 bytes
|
||||
// APDU CLA | 1 byte
|
||||
// APDU INS | 1 byte
|
||||
// APDU P1 | 1 byte
|
||||
// APDU P2 | 1 byte
|
||||
// APDU length | 1 byte
|
||||
// Optional APDU data | arbitrary
|
||||
func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
|
||||
// Construct the message payload, possibly split into multiple chunks
|
||||
apdu := make([]byte, 2, 7+len(data))
|
||||
|
||||
binary.BigEndian.PutUint16(apdu, uint16(5+len(data)))
|
||||
apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...)
|
||||
apdu = append(apdu, data...)
|
||||
|
||||
// Stream all the chunks to the device
|
||||
header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended
|
||||
chunk := make([]byte, 64)
|
||||
space := len(chunk) - len(header)
|
||||
|
||||
for i := 0; len(apdu) > 0; i++ {
|
||||
// Construct the new message to stream
|
||||
chunk = append(chunk[:0], header...)
|
||||
binary.BigEndian.PutUint16(chunk[3:], uint16(i))
|
||||
|
||||
if len(apdu) > space {
|
||||
chunk = append(chunk, apdu[:space]...)
|
||||
apdu = apdu[space:]
|
||||
} else {
|
||||
chunk = append(chunk, apdu...)
|
||||
apdu = nil
|
||||
}
|
||||
// Send over to the device
|
||||
w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk))
|
||||
if _, err := w.device.Write(chunk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Stream the reply back from the wallet in 64 byte chunks
|
||||
var reply []byte
|
||||
chunk = chunk[:64] // Yeah, we surely have enough space
|
||||
for {
|
||||
// Read the next chunk from the Ledger wallet
|
||||
if _, err := io.ReadFull(w.device, chunk); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk))
|
||||
|
||||
// Make sure the transport header matches
|
||||
if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 {
|
||||
return nil, errLedgerReplyInvalidHeader
|
||||
}
|
||||
// If it's the first chunk, retrieve the total message length
|
||||
var payload []byte
|
||||
|
||||
if chunk[3] == 0x00 && chunk[4] == 0x00 {
|
||||
reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7])))
|
||||
payload = chunk[7:]
|
||||
} else {
|
||||
payload = chunk[5:]
|
||||
}
|
||||
// Append to the reply and stop when filled up
|
||||
if left := cap(reply) - len(reply); left > len(payload) {
|
||||
reply = append(reply, payload...)
|
||||
} else {
|
||||
reply = append(reply, payload[:left]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return reply[:len(reply)-2], nil
|
||||
}
|
||||
@@ -1,330 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// This file contains the implementation for interacting with the Trezor hardware
|
||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
||||
|
||||
package usbwallet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor"
|
||||
"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/log"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
|
||||
// this case, the calling application should display a pinpad and send back the
|
||||
// encoded passphrase.
|
||||
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
||||
|
||||
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
||||
// if the device replies with a mismatching header. This usually means the device
|
||||
// is in browser mode.
|
||||
var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
|
||||
|
||||
// trezorDriver implements the communication with a Trezor hardware wallet.
|
||||
type trezorDriver struct {
|
||||
device io.ReadWriter // USB device connection to communicate through
|
||||
version [3]uint32 // Current version of the Trezor firmware
|
||||
label string // Current textual label of the Trezor device
|
||||
pinwait bool // Flags whether the device is waiting for PIN entry
|
||||
failure error // Any failure that would make the device unusable
|
||||
log log.Logger // Contextual logger to tag the trezor with its id
|
||||
}
|
||||
|
||||
// newTrezorDriver creates a new instance of a Trezor USB protocol driver.
|
||||
func newTrezorDriver(logger log.Logger) driver {
|
||||
return &trezorDriver{
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Status implements accounts.Wallet, always whether the Trezor is opened, closed
|
||||
// or whether the Ethereum app was not started on it.
|
||||
func (w *trezorDriver) Status() (string, error) {
|
||||
if w.failure != nil {
|
||||
return fmt.Sprintf("Failed: %v", w.failure), w.failure
|
||||
}
|
||||
if w.device == nil {
|
||||
return "Closed", w.failure
|
||||
}
|
||||
if w.pinwait {
|
||||
return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure
|
||||
}
|
||||
return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure
|
||||
}
|
||||
|
||||
// Open implements usbwallet.driver, attempting to initialize the connection to
|
||||
// the Trezor hardware wallet. Initializing the Trezor is a two phase operation:
|
||||
// * The first phase is to initialize the connection and read the wallet's
|
||||
// features. This phase is invoked is the provided passphrase is empty. The
|
||||
// device will display the pinpad as a result and will return an appropriate
|
||||
// error to notify the user that a second open phase is needed.
|
||||
// * The second phase is to unlock access to the Trezor, which is done by the
|
||||
// user actually providing a passphrase mapping a keyboard keypad to the pin
|
||||
// number of the user (shuffled according to the pinpad displayed).
|
||||
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||
w.device, w.failure = device, nil
|
||||
|
||||
// If phase 1 is requested, init the connection and wait for user callback
|
||||
if passphrase == "" {
|
||||
// If we're already waiting for a PIN entry, insta-return
|
||||
if w.pinwait {
|
||||
return ErrTrezorPINNeeded
|
||||
}
|
||||
// Initialize a connection to the device
|
||||
features := new(trezor.Features)
|
||||
if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil {
|
||||
return err
|
||||
}
|
||||
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
||||
w.label = features.GetLabel()
|
||||
|
||||
// Do a manual ping, forcing the device to ask for its PIN
|
||||
askPin := true
|
||||
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Only return the PIN request if the device wasn't unlocked until now
|
||||
if res == 1 {
|
||||
return nil // Device responded with trezor.Success
|
||||
}
|
||||
w.pinwait = true
|
||||
return ErrTrezorPINNeeded
|
||||
}
|
||||
// Phase 2 requested with actual PIN entry
|
||||
w.pinwait = false
|
||||
|
||||
if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil {
|
||||
w.failure = err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements usbwallet.driver, cleaning up and metadata maintained within
|
||||
// the Trezor driver.
|
||||
func (w *trezorDriver) Close() error {
|
||||
w.version, w.label, w.pinwait = [3]uint32{}, "", false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Heartbeat implements usbwallet.driver, performing a sanity check against the
|
||||
// Trezor to see if it's still online.
|
||||
func (w *trezorDriver) Heartbeat() error {
|
||||
if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil {
|
||||
w.failure = err
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Derive implements usbwallet.driver, sending a derivation request to the Trezor
|
||||
// and returning the Ethereum address located on that derivation path.
|
||||
func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
|
||||
return w.trezorDerive(path)
|
||||
}
|
||||
|
||||
// SignTx implements usbwallet.driver, sending the transaction to the Trezor and
|
||||
// waiting for the user to confirm or deny the transaction.
|
||||
func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||
if w.device == nil {
|
||||
return common.Address{}, nil, accounts.ErrWalletClosed
|
||||
}
|
||||
return w.trezorSign(path, tx, chainID)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
address := new(trezor.EthereumAddress)
|
||||
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
return common.BytesToAddress(address.GetAddress()), nil
|
||||
}
|
||||
|
||||
// trezorSign sends the transaction to the Trezor wallet, and waits for the user
|
||||
// to confirm or deny the transaction.
|
||||
func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||
// Create the transaction initiation message
|
||||
data := tx.Data()
|
||||
length := uint32(len(data))
|
||||
|
||||
request := &trezor.EthereumSignTx{
|
||||
AddressN: derivationPath,
|
||||
Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(),
|
||||
GasPrice: tx.GasPrice().Bytes(),
|
||||
GasLimit: new(big.Int).SetUint64(tx.Gas()).Bytes(),
|
||||
Value: tx.Value().Bytes(),
|
||||
DataLength: &length,
|
||||
}
|
||||
if to := tx.To(); to != nil {
|
||||
request.To = (*to)[:] // Non contract deploy, set recipient explicitly
|
||||
}
|
||||
if length > 1024 { // Send the data chunked if that was requested
|
||||
request.DataInitialChunk, data = data[:1024], data[1024:]
|
||||
} else {
|
||||
request.DataInitialChunk, data = data, nil
|
||||
}
|
||||
if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?)
|
||||
id := uint32(chainID.Int64())
|
||||
request.ChainId = &id
|
||||
}
|
||||
// Send the initiation message and stream content until a signature is returned
|
||||
response := new(trezor.EthereumTxRequest)
|
||||
if _, err := w.trezorExchange(request, response); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
for response.DataLength != nil && int(*response.DataLength) <= len(data) {
|
||||
chunk := data[:*response.DataLength]
|
||||
data = data[*response.DataLength:]
|
||||
|
||||
if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
}
|
||||
// Extract the Ethereum signature and do a sanity validation
|
||||
if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 {
|
||||
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||
}
|
||||
signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
|
||||
|
||||
// Create the correct signer and signature transform based on the chain ID
|
||||
var signer types.Signer
|
||||
if chainID == nil {
|
||||
signer = new(types.HomesteadSigner)
|
||||
} else {
|
||||
signer = types.NewEIP155Signer(chainID)
|
||||
signature[64] = 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 {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
sender, err := types.Sender(signer, signed)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
return sender, signed, nil
|
||||
}
|
||||
|
||||
// trezorExchange performs a data exchange with the Trezor wallet, sending it a
|
||||
// message and retrieving the response. If multiple responses are possible, the
|
||||
// method will also return the index of the destination object used.
|
||||
func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) {
|
||||
// Construct the original message payload to chunk up
|
||||
data, err := proto.Marshal(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
payload := make([]byte, 8+len(data))
|
||||
copy(payload, []byte{0x23, 0x23})
|
||||
binary.BigEndian.PutUint16(payload[2:], trezor.Type(req))
|
||||
binary.BigEndian.PutUint32(payload[4:], uint32(len(data)))
|
||||
copy(payload[8:], data)
|
||||
|
||||
// Stream all the chunks to the device
|
||||
chunk := make([]byte, 64)
|
||||
chunk[0] = 0x3f // Report ID magic number
|
||||
|
||||
for len(payload) > 0 {
|
||||
// Construct the new message to stream, padding with zeroes if needed
|
||||
if len(payload) > 63 {
|
||||
copy(chunk[1:], payload[:63])
|
||||
payload = payload[63:]
|
||||
} else {
|
||||
copy(chunk[1:], payload)
|
||||
copy(chunk[1+len(payload):], make([]byte, 63-len(payload)))
|
||||
payload = nil
|
||||
}
|
||||
// Send over to the device
|
||||
w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk))
|
||||
if _, err := w.device.Write(chunk); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
// Stream the reply back from the wallet in 64 byte chunks
|
||||
var (
|
||||
kind uint16
|
||||
reply []byte
|
||||
)
|
||||
for {
|
||||
// Read the next chunk from the Trezor wallet
|
||||
if _, err := io.ReadFull(w.device, chunk); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk))
|
||||
|
||||
// Make sure the transport header matches
|
||||
if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) {
|
||||
return 0, errTrezorReplyInvalidHeader
|
||||
}
|
||||
// If it's the first chunk, retrieve the reply message type and total message length
|
||||
var payload []byte
|
||||
|
||||
if len(reply) == 0 {
|
||||
kind = binary.BigEndian.Uint16(chunk[3:5])
|
||||
reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9])))
|
||||
payload = chunk[9:]
|
||||
} else {
|
||||
payload = chunk[1:]
|
||||
}
|
||||
// Append to the reply and stop when filled up
|
||||
if left := cap(reply) - len(reply); left > len(payload) {
|
||||
reply = append(reply, payload...)
|
||||
} else {
|
||||
reply = append(reply, payload[:left]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Try to parse the reply into the requested reply message
|
||||
if kind == uint16(trezor.MessageType_MessageType_Failure) {
|
||||
// Trezor returned a failure, extract and return the message
|
||||
failure := new(trezor.Failure)
|
||||
if err := proto.Unmarshal(reply, failure); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, errors.New("trezor: " + failure.GetMessage())
|
||||
}
|
||||
if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) {
|
||||
// Trezor is waiting for user confirmation, ack and wait for the next message
|
||||
return w.trezorExchange(&trezor.ButtonAck{}, results...)
|
||||
}
|
||||
for i, res := range results {
|
||||
if trezor.Type(res) == kind {
|
||||
return i, proto.Unmarshal(reply, res)
|
||||
}
|
||||
}
|
||||
expected := make([]string, len(results))
|
||||
for i, res := range results {
|
||||
expected[i] = trezor.Name(trezor.Type(res))
|
||||
}
|
||||
return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind))
|
||||
}
|
||||
@@ -1,562 +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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package usbwallet implements support for USB hardware wallets.
|
||||
package usbwallet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
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"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/karalabe/hid"
|
||||
)
|
||||
|
||||
// Maximum time between wallet health checks to detect USB unplugs.
|
||||
const heartbeatCycle = time.Second
|
||||
|
||||
// Minimum time to wait between self derivation attempts, even it the user is
|
||||
// requesting accounts like crazy.
|
||||
const selfDeriveThrottling = time.Second
|
||||
|
||||
// driver defines the vendor specific functionality hardware wallets instances
|
||||
// must implement to allow using them with the wallet lifecycle management.
|
||||
type driver interface {
|
||||
// Status returns a textual status to aid the user in the current state of the
|
||||
// wallet. It also returns an error indicating any failure the wallet might have
|
||||
// encountered.
|
||||
Status() (string, error)
|
||||
|
||||
// Open initializes access to a wallet instance. The passphrase parameter may
|
||||
// or may not be used by the implementation of a particular wallet instance.
|
||||
Open(device io.ReadWriter, passphrase string) error
|
||||
|
||||
// Close releases any resources held by an open wallet instance.
|
||||
Close() error
|
||||
|
||||
// Heartbeat performs a sanity check against the hardware wallet to see if it
|
||||
// is still online and healthy.
|
||||
Heartbeat() error
|
||||
|
||||
// Derive sends a derivation request to the USB device and returns the Ethereum
|
||||
// address located on that path.
|
||||
Derive(path accounts.DerivationPath) (common.Address, error)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// wallet represents the common functionality shared by all USB hardware
|
||||
// wallets to prevent reimplementing the same complex maintenance mechanisms
|
||||
// for different vendors.
|
||||
type wallet struct {
|
||||
hub *Hub // USB hub scanning
|
||||
driver driver // Hardware implementation of the low level device operations
|
||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||
|
||||
info hid.DeviceInfo // Known USB device infos about the wallet
|
||||
device *hid.Device // USB device advertising itself as a hardware wallet
|
||||
|
||||
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||
|
||||
deriveNextPath accounts.DerivationPath // Next derivation path for account auto-discovery
|
||||
deriveNextAddr common.Address // Next derived account address for auto-discovery
|
||||
deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with
|
||||
deriveReq chan chan struct{} // Channel to request a self-derivation on
|
||||
deriveQuit chan chan error // Channel to terminate the self-deriver with
|
||||
|
||||
healthQuit chan chan error
|
||||
|
||||
// Locking a hardware wallet is a bit special. Since hardware devices are lower
|
||||
// performing, any communication with them might take a non negligible amount of
|
||||
// time. Worse still, waiting for user confirmation can take arbitrarily long,
|
||||
// but exclusive communication must be upheld during. Locking the entire wallet
|
||||
// in the mean time however would stall any parts of the system that don't want
|
||||
// to communicate, just read some state (e.g. list the accounts).
|
||||
//
|
||||
// As such, a hardware wallet needs two locks to function correctly. A state
|
||||
// lock can be used to protect the wallet's software-side internal state, which
|
||||
// must not be held exclusively during hardware communication. A communication
|
||||
// lock can be used to achieve exclusive access to the device itself, this one
|
||||
// however should allow "skipping" waiting for operations that might want to
|
||||
// use the device, but can live without too (e.g. account self-derivation).
|
||||
//
|
||||
// Since we have two locks, it's important to know how to properly use them:
|
||||
// - Communication requires the `device` to not change, so obtaining the
|
||||
// commsLock should be done after having a stateLock.
|
||||
// - Communication must not disable read access to the wallet state, so it
|
||||
// must only ever hold a *read* lock to stateLock.
|
||||
commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked
|
||||
stateLock sync.RWMutex // Protects read and write access to the wallet struct fields
|
||||
|
||||
log log.Logger // Contextual logger to tag the base with its id
|
||||
}
|
||||
|
||||
// URL implements accounts.Wallet, returning the URL of the USB hardware device.
|
||||
func (w *wallet) URL() accounts.URL {
|
||||
return *w.url // Immutable, no need for a lock
|
||||
}
|
||||
|
||||
// Status implements accounts.Wallet, returning a custom status message from the
|
||||
// underlying vendor-specific hardware wallet implementation.
|
||||
func (w *wallet) Status() (string, error) {
|
||||
w.stateLock.RLock() // No device communication, state lock is enough
|
||||
defer w.stateLock.RUnlock()
|
||||
|
||||
status, failure := w.driver.Status()
|
||||
if w.device == nil {
|
||||
return "Closed", failure
|
||||
}
|
||||
return status, failure
|
||||
}
|
||||
|
||||
// Open implements accounts.Wallet, attempting to open a USB connection to the
|
||||
// hardware wallet.
|
||||
func (w *wallet) Open(passphrase string) error {
|
||||
w.stateLock.Lock() // State lock is enough since there's no connection yet at this point
|
||||
defer w.stateLock.Unlock()
|
||||
|
||||
// If the device was already opened once, refuse to try again
|
||||
if w.paths != nil {
|
||||
return accounts.ErrWalletAlreadyOpen
|
||||
}
|
||||
// Make sure the actual device connection is done only once
|
||||
if w.device == nil {
|
||||
device, err := w.info.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.device = device
|
||||
w.commsLock = make(chan struct{}, 1)
|
||||
w.commsLock <- struct{}{} // Enable lock
|
||||
}
|
||||
// Delegate device initialization to the underlying driver
|
||||
if err := w.driver.Open(w.device, passphrase); err != nil {
|
||||
return err
|
||||
}
|
||||
// Connection successful, start life-cycle management
|
||||
w.paths = make(map[common.Address]accounts.DerivationPath)
|
||||
|
||||
w.deriveReq = make(chan chan struct{})
|
||||
w.deriveQuit = make(chan chan error)
|
||||
w.healthQuit = make(chan chan error)
|
||||
|
||||
go w.heartbeat()
|
||||
go w.selfDerive()
|
||||
|
||||
// Notify anyone listening for wallet events that a new device is accessible
|
||||
go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// heartbeat is a health check loop for the USB wallets to periodically verify
|
||||
// whether they are still present or if they malfunctioned.
|
||||
func (w *wallet) heartbeat() {
|
||||
w.log.Debug("USB wallet health-check started")
|
||||
defer w.log.Debug("USB wallet health-check stopped")
|
||||
|
||||
// Execute heartbeat checks until termination or error
|
||||
var (
|
||||
errc chan error
|
||||
err error
|
||||
)
|
||||
for errc == nil && err == nil {
|
||||
// Wait until termination is requested or the heartbeat cycle arrives
|
||||
select {
|
||||
case errc = <-w.healthQuit:
|
||||
// Termination requested
|
||||
continue
|
||||
case <-time.After(heartbeatCycle):
|
||||
// Heartbeat time
|
||||
}
|
||||
// Execute a tiny data exchange to see responsiveness
|
||||
w.stateLock.RLock()
|
||||
if w.device == nil {
|
||||
// Terminated while waiting for the lock
|
||||
w.stateLock.RUnlock()
|
||||
continue
|
||||
}
|
||||
<-w.commsLock // Don't lock state while resolving version
|
||||
err = w.driver.Heartbeat()
|
||||
w.commsLock <- struct{}{}
|
||||
w.stateLock.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
w.stateLock.Lock() // Lock state to tear the wallet down
|
||||
w.close()
|
||||
w.stateLock.Unlock()
|
||||
}
|
||||
// Ignore non hardware related errors
|
||||
err = nil
|
||||
}
|
||||
// In case of error, wait for termination
|
||||
if err != nil {
|
||||
w.log.Debug("USB wallet health-check failed", "err", err)
|
||||
errc = <-w.healthQuit
|
||||
}
|
||||
errc <- err
|
||||
}
|
||||
|
||||
// Close implements accounts.Wallet, closing the USB connection to the device.
|
||||
func (w *wallet) Close() error {
|
||||
// Ensure the wallet was opened
|
||||
w.stateLock.RLock()
|
||||
hQuit, dQuit := w.healthQuit, w.deriveQuit
|
||||
w.stateLock.RUnlock()
|
||||
|
||||
// Terminate the health checks
|
||||
var herr error
|
||||
if hQuit != nil {
|
||||
errc := make(chan error)
|
||||
hQuit <- errc
|
||||
herr = <-errc // Save for later, we *must* close the USB
|
||||
}
|
||||
// Terminate the self-derivations
|
||||
var derr error
|
||||
if dQuit != nil {
|
||||
errc := make(chan error)
|
||||
dQuit <- errc
|
||||
derr = <-errc // Save for later, we *must* close the USB
|
||||
}
|
||||
// Terminate the device connection
|
||||
w.stateLock.Lock()
|
||||
defer w.stateLock.Unlock()
|
||||
|
||||
w.healthQuit = nil
|
||||
w.deriveQuit = nil
|
||||
w.deriveReq = nil
|
||||
|
||||
if err := w.close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if herr != nil {
|
||||
return herr
|
||||
}
|
||||
return derr
|
||||
}
|
||||
|
||||
// close is the internal wallet closer that terminates the USB connection and
|
||||
// resets all the fields to their defaults.
|
||||
//
|
||||
// Note, close assumes the state lock is held!
|
||||
func (w *wallet) close() error {
|
||||
// Allow duplicate closes, especially for health-check failures
|
||||
if w.device == nil {
|
||||
return nil
|
||||
}
|
||||
// Close the device, clear everything, then return
|
||||
w.device.Close()
|
||||
w.device = nil
|
||||
|
||||
w.accounts, w.paths = nil, nil
|
||||
w.driver.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Accounts implements accounts.Wallet, returning the list of accounts pinned to
|
||||
// the USB hardware wallet. If self-derivation was enabled, the account list is
|
||||
// periodically expanded based on current chain state.
|
||||
func (w *wallet) Accounts() []accounts.Account {
|
||||
// Attempt self-derivation if it's running
|
||||
reqc := make(chan struct{}, 1)
|
||||
select {
|
||||
case w.deriveReq <- reqc:
|
||||
// Self-derivation request accepted, wait for it
|
||||
<-reqc
|
||||
default:
|
||||
// Self-derivation offline, throttled or busy, skip
|
||||
}
|
||||
// Return whatever account list we ended up with
|
||||
w.stateLock.RLock()
|
||||
defer w.stateLock.RUnlock()
|
||||
|
||||
cpy := make([]accounts.Account, len(w.accounts))
|
||||
copy(cpy, w.accounts)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// selfDerive is an account derivation loop that upon request attempts to find
|
||||
// new non-zero accounts.
|
||||
func (w *wallet) selfDerive() {
|
||||
w.log.Debug("USB wallet self-derivation started")
|
||||
defer w.log.Debug("USB wallet self-derivation stopped")
|
||||
|
||||
// Execute self-derivations until termination or error
|
||||
var (
|
||||
reqc chan struct{}
|
||||
errc chan error
|
||||
err error
|
||||
)
|
||||
for errc == nil && err == nil {
|
||||
// Wait until either derivation or termination is requested
|
||||
select {
|
||||
case errc = <-w.deriveQuit:
|
||||
// Termination requested
|
||||
continue
|
||||
case reqc = <-w.deriveReq:
|
||||
// Account discovery requested
|
||||
}
|
||||
// Derivation needs a chain and device access, skip if either unavailable
|
||||
w.stateLock.RLock()
|
||||
if w.device == nil || w.deriveChain == nil {
|
||||
w.stateLock.RUnlock()
|
||||
reqc <- struct{}{}
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case <-w.commsLock:
|
||||
default:
|
||||
w.stateLock.RUnlock()
|
||||
reqc <- struct{}{}
|
||||
continue
|
||||
}
|
||||
// Device lock obtained, derive the next batch of accounts
|
||||
var (
|
||||
accs []accounts.Account
|
||||
paths []accounts.DerivationPath
|
||||
|
||||
nextAddr = w.deriveNextAddr
|
||||
nextPath = w.deriveNextPath
|
||||
|
||||
context = context.Background()
|
||||
)
|
||||
for empty := false; !empty; {
|
||||
// Retrieve the next derived Ethereum account
|
||||
if nextAddr == (common.Address{}) {
|
||||
if nextAddr, err = w.driver.Derive(nextPath); err != nil {
|
||||
w.log.Warn("USB wallet account derivation failed", "err", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Check the account's status against the current chain state
|
||||
var (
|
||||
balance *big.Int
|
||||
nonce uint64
|
||||
)
|
||||
balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil)
|
||||
if err != nil {
|
||||
w.log.Warn("USB wallet balance retrieval failed", "err", err)
|
||||
break
|
||||
}
|
||||
nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil)
|
||||
if err != nil {
|
||||
w.log.Warn("USB wallet nonce retrieval failed", "err", err)
|
||||
break
|
||||
}
|
||||
// If the next account is empty, stop self-derivation, but add it nonetheless
|
||||
if balance.Sign() == 0 && nonce == 0 {
|
||||
empty = true
|
||||
}
|
||||
// We've just self-derived a new account, start tracking it locally
|
||||
path := make(accounts.DerivationPath, len(nextPath))
|
||||
copy(path[:], nextPath[:])
|
||||
paths = append(paths, path)
|
||||
|
||||
account := accounts.Account{
|
||||
Address: nextAddr,
|
||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
||||
}
|
||||
accs = append(accs, account)
|
||||
|
||||
// Display a log message to the user for new (or previously empty accounts)
|
||||
if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) {
|
||||
w.log.Info("USB wallet discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce)
|
||||
}
|
||||
// Fetch the next potential account
|
||||
if !empty {
|
||||
nextAddr = common.Address{}
|
||||
nextPath[len(nextPath)-1]++
|
||||
}
|
||||
}
|
||||
// Self derivation complete, release device lock
|
||||
w.commsLock <- struct{}{}
|
||||
w.stateLock.RUnlock()
|
||||
|
||||
// Insert any accounts successfully derived
|
||||
w.stateLock.Lock()
|
||||
for i := 0; i < len(accs); i++ {
|
||||
if _, ok := w.paths[accs[i].Address]; !ok {
|
||||
w.accounts = append(w.accounts, accs[i])
|
||||
w.paths[accs[i].Address] = paths[i]
|
||||
}
|
||||
}
|
||||
// Shift the self-derivation forward
|
||||
// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
|
||||
w.deriveNextAddr = nextAddr
|
||||
w.deriveNextPath = nextPath
|
||||
w.stateLock.Unlock()
|
||||
|
||||
// Notify the user of termination and loop after a bit of time (to avoid trashing)
|
||||
reqc <- struct{}{}
|
||||
if err == nil {
|
||||
select {
|
||||
case errc = <-w.deriveQuit:
|
||||
// Termination requested, abort
|
||||
case <-time.After(selfDeriveThrottling):
|
||||
// Waited enough, willing to self-derive again
|
||||
}
|
||||
}
|
||||
}
|
||||
// In case of error, wait for termination
|
||||
if err != nil {
|
||||
w.log.Debug("USB wallet self-derivation failed", "err", err)
|
||||
errc = <-w.deriveQuit
|
||||
}
|
||||
errc <- err
|
||||
}
|
||||
|
||||
// Contains implements accounts.Wallet, returning whether a particular account is
|
||||
// or is not pinned into this wallet instance. Although we could attempt to resolve
|
||||
// unpinned accounts, that would be an non-negligible hardware operation.
|
||||
func (w *wallet) Contains(account accounts.Account) bool {
|
||||
w.stateLock.RLock()
|
||||
defer w.stateLock.RUnlock()
|
||||
|
||||
_, exists := w.paths[account.Address]
|
||||
return exists
|
||||
}
|
||||
|
||||
// Derive implements accounts.Wallet, deriving a new account at the specific
|
||||
// derivation path. If pin is set to true, the account will be added to the list
|
||||
// of tracked accounts.
|
||||
func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||
// Try to derive the actual account and update its URL if successful
|
||||
w.stateLock.RLock() // Avoid device disappearing during derivation
|
||||
|
||||
if w.device == nil {
|
||||
w.stateLock.RUnlock()
|
||||
return accounts.Account{}, accounts.ErrWalletClosed
|
||||
}
|
||||
<-w.commsLock // Avoid concurrent hardware access
|
||||
address, err := w.driver.Derive(path)
|
||||
w.commsLock <- struct{}{}
|
||||
|
||||
w.stateLock.RUnlock()
|
||||
|
||||
// If an error occurred or no pinning was requested, return
|
||||
if err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
account := accounts.Account{
|
||||
Address: address,
|
||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
||||
}
|
||||
if !pin {
|
||||
return account, nil
|
||||
}
|
||||
// Pinning needs to modify the state
|
||||
w.stateLock.Lock()
|
||||
defer w.stateLock.Unlock()
|
||||
|
||||
if _, ok := w.paths[address]; !ok {
|
||||
w.accounts = append(w.accounts, account)
|
||||
w.paths[address] = path
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// SelfDerive implements accounts.Wallet, trying to discover accounts that the
|
||||
// user used previously (based on the chain state), but ones that he/she did not
|
||||
// explicitly pin to the wallet manually. To avoid chain head monitoring, self
|
||||
// derivation only runs during account listing (and even then throttled).
|
||||
func (w *wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||
w.stateLock.Lock()
|
||||
defer w.stateLock.Unlock()
|
||||
|
||||
w.deriveNextPath = make(accounts.DerivationPath, len(base))
|
||||
copy(w.deriveNextPath[:], base[:])
|
||||
|
||||
w.deriveNextAddr = common.Address{}
|
||||
w.deriveChain = chain
|
||||
}
|
||||
|
||||
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
||||
// supported for hardware wallets, so this method will always return an error.
|
||||
func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||
return nil, accounts.ErrNotSupported
|
||||
}
|
||||
|
||||
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
||||
// wallet to request a confirmation from the user. It returns either the signed
|
||||
// transaction or a failure if the user denied the transaction.
|
||||
//
|
||||
// Note, if the version of the Ethereum application running on the Ledger wallet is
|
||||
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
|
||||
// will be returned opposed to silently signing in Homestead mode.
|
||||
func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
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 and verify the sender to avoid hardware fault surprises
|
||||
sender, signed, err := w.driver.SignTx(path, tx, chainID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sender != account.Address {
|
||||
return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex())
|
||||
}
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||
// data is not supported for Ledger wallets, so this method will always return
|
||||
// an error.
|
||||
func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
||||
return w.SignHash(account, hash)
|
||||
}
|
||||
|
||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||
// transaction with the given account using passphrase as extra authentication.
|
||||
// Since USB wallets don't rely on passphrases, these are silently ignored.
|
||||
func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||
return w.SignTx(account, tx, chainID)
|
||||
}
|
||||
40
appveyor.yml
40
appveyor.yml
@@ -1,40 +0,0 @@
|
||||
os: Visual Studio 2015
|
||||
|
||||
# Clone directly into GOPATH.
|
||||
clone_folder: C:\gopath\src\github.com\ethereum\go-ethereum
|
||||
clone_depth: 5
|
||||
version: "{branch}.{build}"
|
||||
environment:
|
||||
global:
|
||||
GOPATH: C:\gopath
|
||||
CC: gcc.exe
|
||||
matrix:
|
||||
- GETH_ARCH: amd64
|
||||
MSYS2_ARCH: x86_64
|
||||
MSYS2_BITS: 64
|
||||
MSYSTEM: MINGW64
|
||||
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%
|
||||
|
||||
install:
|
||||
- git submodule update --init
|
||||
- rmdir C:\go /s /q
|
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.3.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.10.3.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- go version
|
||||
- gcc --version
|
||||
|
||||
build_script:
|
||||
- go run build\ci.go install
|
||||
|
||||
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
|
||||
|
||||
test_script:
|
||||
- set CGO_ENABLED=1
|
||||
- go run build\ci.go test -coverage
|
||||
@@ -1,44 +0,0 @@
|
||||
# Debian Packaging
|
||||
|
||||
Tagged releases and develop branch commits are available as installable Debian packages
|
||||
for Ubuntu. Packages are built for the all Ubuntu versions which are supported by
|
||||
Canonical.
|
||||
|
||||
Packages of develop branch commits have suffix -unstable and cannot be installed alongside
|
||||
the stable version. Switching between release streams requires user intervention.
|
||||
|
||||
The packages are built and served by launchpad.net. We generate a Debian source package
|
||||
for each distribution and upload it. Their builder picks up the source package, builds it
|
||||
and installs the new version into the PPA repository. Launchpad requires a valid signature
|
||||
by a team member for source package uploads. The signing key is stored in an environment
|
||||
variable which Travis CI makes available to certain builds.
|
||||
|
||||
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
|
||||
version that is available in the main Ubuntu repository. In order to make this possible,
|
||||
our PPA depends on the ~gophers/ubuntu/archive PPA. Our source package build-depends on
|
||||
golang-1.10, which is co-installable alongside the regular golang package. PPA dependencies
|
||||
can be edited at https://launchpad.net/%7Eethereum/+archive/ubuntu/ethereum/+edit-dependencies
|
||||
|
||||
## Building Packages Locally (for testing)
|
||||
|
||||
You need to run Ubuntu to do test packaging.
|
||||
|
||||
Add the gophers PPA and install Go 1.10 and Debian packaging tools:
|
||||
|
||||
$ sudo apt-add-repository ppa:gophers/ubuntu/archive
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install build-essential golang-1.10 devscripts debhelper
|
||||
|
||||
Create the source packages:
|
||||
|
||||
$ go run build/ci.go debsrc -workdir dist
|
||||
|
||||
Then go into the source package directory for your running distribution and build the package:
|
||||
|
||||
$ cd dist/ethereum-unstable-1.6.0+xenial
|
||||
$ dpkg-buildpackage
|
||||
|
||||
Built packages are placed in the dist/ directory.
|
||||
|
||||
$ cd ..
|
||||
$ dpkg-deb -c geth-unstable_1.6.0+xenial_amd64.deb
|
||||
1046
build/ci.go
1046
build/ci.go
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
||||
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||
|
||||
* git build of {{.Env.Commit}}
|
||||
|
||||
-- {{.Author}} {{.Time}}
|
||||
@@ -1,25 +0,0 @@
|
||||
Source: {{.Name}}
|
||||
Section: science
|
||||
Priority: extra
|
||||
Maintainer: {{.Author}}
|
||||
Build-Depends: debhelper (>= 8.0.0), golang-1.10
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: https://ethereum.org
|
||||
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||
Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||
|
||||
Package: {{.Name}}
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, {{.ExeList}}
|
||||
Description: Meta-package to install geth and other tools
|
||||
Meta-package to install geth and other tools
|
||||
|
||||
{{range .Executables}}
|
||||
Package: {{$.ExeName .}}
|
||||
Conflicts: {{$.ExeConflicts .}}
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Built-Using: ${misc:Built-Using}
|
||||
Description: {{.Description}}
|
||||
{{.Description}}
|
||||
{{end}}
|
||||
@@ -1,14 +0,0 @@
|
||||
Copyright 2016 The go-ethereum Authors
|
||||
|
||||
go-ethereum is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
go-ethereum is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
@@ -1 +0,0 @@
|
||||
AUTHORS
|
||||
@@ -1 +0,0 @@
|
||||
build/bin/{{.Name}} usr/bin
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
override_dh_auto_build:
|
||||
build/env.sh /usr/lib/go-1.10/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
||||
|
||||
override_dh_auto_test:
|
||||
|
||||
%:
|
||||
dh $@
|
||||
30
build/env.sh
30
build/env.sh
@@ -1,30 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f "build/env.sh" ]; then
|
||||
echo "$0 must be run from the root of the repository."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Create fake Go workspace if it doesn't exist yet.
|
||||
workspace="$PWD/build/_workspace"
|
||||
root="$PWD"
|
||||
ethdir="$workspace/src/github.com/ethereum"
|
||||
if [ ! -L "$ethdir/go-ethereum" ]; then
|
||||
mkdir -p "$ethdir"
|
||||
cd "$ethdir"
|
||||
ln -s ../../../../../. go-ethereum
|
||||
cd "$root"
|
||||
fi
|
||||
|
||||
# Set up the environment to use the workspace.
|
||||
GOPATH="$workspace"
|
||||
export GOPATH
|
||||
|
||||
# Run the command inside the workspace.
|
||||
cd "$ethdir/go-ethereum"
|
||||
PWD="$ethdir/go-ethereum"
|
||||
|
||||
# Launch the arguments with the configured environment.
|
||||
exec "$@"
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
find_files() {
|
||||
find . ! \( \
|
||||
\( \
|
||||
-path '.github' \
|
||||
-o -path './build/_workspace' \
|
||||
-o -path './build/bin' \
|
||||
-o -path './crypto/bn256' \
|
||||
-o -path '*/vendor/*' \
|
||||
\) -prune \
|
||||
\) -name '*.go'
|
||||
}
|
||||
|
||||
GOFMT="gofmt -s -w"
|
||||
GOIMPORTS="goimports -w"
|
||||
find_files | xargs $GOFMT
|
||||
find_files | xargs $GOIMPORTS
|
||||
@@ -1,57 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.ethereum</groupId>
|
||||
<artifactId>geth</artifactId>
|
||||
<version>{{.Version}}</version>
|
||||
<packaging>aar</packaging>
|
||||
|
||||
<name>Android Ethereum Client</name>
|
||||
<description>Android port of the go-ethereum libraries and node</description>
|
||||
<url>https://github.com/ethereum/go-ethereum</url>
|
||||
<inceptionYear>2015</inceptionYear>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>GNU Lesser General Public License, Version 3.0</name>
|
||||
<url>https://www.gnu.org/licenses/lgpl-3.0.en.html</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<organization>
|
||||
<name>Ethereum</name>
|
||||
<url>https://ethereum.org</url>
|
||||
</organization>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>karalabe</id>
|
||||
<name>Péter Szilágyi</name>
|
||||
<email>peterke@gmail.com</email>
|
||||
<url>https://github.com/karalabe</url>
|
||||
<properties>
|
||||
<picUrl>https://www.gravatar.com/avatar/2ecbf0f5b4b79eebf8c193e5d324357f?s=256</picUrl>
|
||||
</properties>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<contributors>{{range .Contributors}}
|
||||
<contributor>
|
||||
<name>{{.Name}}</name>
|
||||
<email>{{.Email}}</email>
|
||||
</contributor>{{end}}
|
||||
</contributors>
|
||||
|
||||
<issueManagement>
|
||||
<system>GitHub Issues</system>
|
||||
<url>https://github.com/ethereum/go-ethereum/issues/</url>
|
||||
</issueManagement>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/ethereum/go-ethereum</url>
|
||||
</scm>
|
||||
</project>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user