1
.gitattributes
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto
|
||||||
|
*.sol linguist-language=Solidity
|
||||||
|
|
|
||||||
7
.github/CODEOWNERS
vendored
|
|
@ -2,8 +2,15 @@
|
||||||
# Each line is a file pattern followed by one or more owners.
|
# Each line is a file pattern followed by one or more owners.
|
||||||
|
|
||||||
accounts/usbwallet @karalabe
|
accounts/usbwallet @karalabe
|
||||||
|
accounts/abi @gballet
|
||||||
consensus @karalabe
|
consensus @karalabe
|
||||||
core/ @karalabe @holiman
|
core/ @karalabe @holiman
|
||||||
eth/ @karalabe
|
eth/ @karalabe
|
||||||
|
les/ @zsfelfoldi
|
||||||
|
light/ @zsfelfoldi
|
||||||
mobile/ @karalabe
|
mobile/ @karalabe
|
||||||
p2p/ @fjl @zsfelfoldi
|
p2p/ @fjl @zsfelfoldi
|
||||||
|
p2p/simulations @zelig @nonsense @janos @justelad
|
||||||
|
p2p/protocols @zelig @nonsense @janos @justelad
|
||||||
|
p2p/testing @zelig @nonsense @janos @justelad
|
||||||
|
whisper/ @gballet @gluk256
|
||||||
|
|
|
||||||
46
.github/CONTRIBUTING.md
vendored
|
|
@ -1,16 +1,40 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thank you for considering to help out with the source code! We welcome
|
||||||
|
contributions from anyone on the internet, and are grateful for even the
|
||||||
|
smallest of fixes!
|
||||||
|
|
||||||
|
If you'd like to contribute to go-ethereum, please fork, fix, commit and send a
|
||||||
|
pull request for the maintainers to review and merge into the main code base. If
|
||||||
|
you wish to submit more complex changes though, please check up with the core
|
||||||
|
devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) to
|
||||||
|
ensure those changes are in line with the general philosophy of the project
|
||||||
|
and/or get some early feedback which can make both your efforts much lighter as
|
||||||
|
well as our review and merge procedures quick and simple.
|
||||||
|
|
||||||
|
## Coding guidelines
|
||||||
|
|
||||||
|
Please make sure your contributions adhere to our coding guidelines:
|
||||||
|
|
||||||
|
* Code must adhere to the official Go
|
||||||
|
[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines
|
||||||
|
(i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||||
|
* Code must be documented adhering to the official Go
|
||||||
|
[commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||||
|
* Pull requests need to be based on and opened against the `master` branch.
|
||||||
|
* Commit messages should be prefixed with the package(s) they modify.
|
||||||
|
* E.g. "eth, rpc: make trace configs optional"
|
||||||
|
|
||||||
## Can I have feature X
|
## Can I have feature X
|
||||||
|
|
||||||
Before you do a feature request please check and make sure that it isn't possible
|
Before you submit a feature request, please check and make sure that it isn't
|
||||||
through some other means. The JavaScript enabled console is a powerful feature
|
possible through some other means. The JavaScript-enabled console is a powerful
|
||||||
in the right hands. Please check our [Bitchin' tricks](https://github.com/ethereum/go-ethereum/wiki/bitchin-tricks) wiki page for more info
|
feature in the right hands. Please check our
|
||||||
|
[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info
|
||||||
and help.
|
and help.
|
||||||
|
|
||||||
## Contributing
|
## Configuration, dependencies, and tests
|
||||||
|
|
||||||
If you'd like to contribute to go-ethereum please fork, fix, commit and
|
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||||
send a pull request. Commits which do not comply with the coding standards
|
for more details on configuring your environment, managing project dependencies
|
||||||
are ignored (use gofmt!).
|
and testing procedures.
|
||||||
|
|
||||||
See [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
|
||||||
for more details on configuring your environment, testing, and
|
|
||||||
dependency management.
|
|
||||||
|
|
|
||||||
11
.github/no-response.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Number of days of inactivity before an Issue is closed for lack of response
|
||||||
|
daysUntilClose: 30
|
||||||
|
# Label requiring a response
|
||||||
|
responseRequiredLabel: "need:more-information"
|
||||||
|
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there has been no response
|
||||||
|
to our request for more information from the original author. With only the
|
||||||
|
information that is currently in the issue, we don't have enough information
|
||||||
|
to take action. Please reach out if you have more relevant information or
|
||||||
|
answers to our questions so that we can investigate further.
|
||||||
17
.github/stale.yml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Number of days of inactivity before an issue becomes stale
|
||||||
|
daysUntilStale: 366
|
||||||
|
# Number of days of inactivity before a stale issue is closed
|
||||||
|
daysUntilClose: 42
|
||||||
|
# Issues with these labels will never be considered stale
|
||||||
|
exemptLabels:
|
||||||
|
- pinned
|
||||||
|
- security
|
||||||
|
# Label to use when marking an issue as stale
|
||||||
|
staleLabel: "status:inactive"
|
||||||
|
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as stale because it has not had
|
||||||
|
recent activity. It will be closed if no further activity occurs. Thank you
|
||||||
|
for your contributions.
|
||||||
|
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||||
|
closeComment: false
|
||||||
6
.gitignore
vendored
|
|
@ -34,8 +34,14 @@ profile.cov
|
||||||
# IdeaIDE
|
# IdeaIDE
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
# dashboard
|
# dashboard
|
||||||
/dashboard/assets/flow-typed
|
/dashboard/assets/flow-typed
|
||||||
/dashboard/assets/node_modules
|
/dashboard/assets/node_modules
|
||||||
/dashboard/assets/stats.json
|
/dashboard/assets/stats.json
|
||||||
/dashboard/assets/bundle.js
|
/dashboard/assets/bundle.js
|
||||||
|
/dashboard/assets/package-lock.json
|
||||||
|
|
||||||
|
**/yarn-error.log
|
||||||
|
|
|
||||||
14
.mailmap
|
|
@ -65,7 +65,8 @@ Enrique Fynn <enriquefynn@gmail.com>
|
||||||
|
|
||||||
Vincent G <caktux@gmail.com>
|
Vincent G <caktux@gmail.com>
|
||||||
|
|
||||||
RJ Catalano <rj@erisindustries.com>
|
RJ Catalano <catalanor0220@gmail.com>
|
||||||
|
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
|
||||||
|
|
||||||
Nchinda Nchinda <nchinda2@gmail.com>
|
Nchinda Nchinda <nchinda2@gmail.com>
|
||||||
|
|
||||||
|
|
@ -109,3 +110,14 @@ Frank Wang <eternnoir@gmail.com>
|
||||||
Gary Rong <garyrong0905@gmail.com>
|
Gary Rong <garyrong0905@gmail.com>
|
||||||
|
|
||||||
Guillaume Nicolas <guin56@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>
|
||||||
|
|
|
||||||
108
.travis.yml
|
|
@ -6,51 +6,56 @@ matrix:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.7.x
|
go: 1.10.x
|
||||||
script:
|
script:
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.8.x
|
go: 1.11.x
|
||||||
script:
|
script:
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# These are the latest Go versions.
|
# These are the latest Go versions.
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.9.x
|
go: 1.12.x
|
||||||
script:
|
script:
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: 1.9.x
|
go: 1.12.x
|
||||||
script:
|
script:
|
||||||
|
- echo "Increase the maximum number of open file descriptors on macOS"
|
||||||
|
- NOFILE=20480
|
||||||
|
- sudo sysctl -w kern.maxfiles=$NOFILE
|
||||||
|
- sudo sysctl -w kern.maxfilesperproc=$NOFILE
|
||||||
|
- sudo launchctl limit maxfiles $NOFILE $NOFILE
|
||||||
|
- sudo launchctl limit maxfiles
|
||||||
|
- ulimit -S -n $NOFILE
|
||||||
|
- ulimit -n
|
||||||
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
- 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 install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# This builder only tests code linters on latest version of Go
|
# This builder only tests code linters on latest version of Go
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: 1.9.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- lint
|
- lint
|
||||||
git:
|
git:
|
||||||
|
|
@ -58,14 +63,13 @@ matrix:
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go lint
|
- go run build/ci.go lint
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA and Linux Azure uploads
|
# This builder does the Ubuntu PPA upload
|
||||||
- os: linux
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
go: 1.12.x
|
||||||
go: 1.9.x
|
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- azure-linux
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
|
|
@ -74,11 +78,29 @@ matrix:
|
||||||
- devscripts
|
- devscripts
|
||||||
- debhelper
|
- debhelper
|
||||||
- dput
|
- dput
|
||||||
- gcc-multilib
|
|
||||||
- fakeroot
|
- fakeroot
|
||||||
|
- python-bzrlib
|
||||||
|
- python-paramiko
|
||||||
|
script:
|
||||||
|
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||||
|
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||||
|
|
||||||
|
# This builder does the Linux Azure uploads
|
||||||
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
|
os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
go: 1.12.x
|
||||||
|
env:
|
||||||
|
- azure-linux
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- gcc-multilib
|
||||||
script:
|
script:
|
||||||
# Build for the primary platforms that Trusty can manage
|
# Build for the primary platforms that Trusty can manage
|
||||||
- go run build/ci.go 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 install
|
||||||
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go install -arch 386
|
- go run build/ci.go install -arch 386
|
||||||
|
|
@ -98,11 +120,12 @@ matrix:
|
||||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Linux Azure MIPS xgo uploads
|
# This builder does the Linux Azure MIPS xgo uploads
|
||||||
- os: linux
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: 1.9.x
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
git:
|
git:
|
||||||
|
|
@ -125,8 +148,9 @@ matrix:
|
||||||
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Android Maven and Azure uploads
|
# This builder does the Android Maven and Azure uploads
|
||||||
- os: linux
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
dist: precise # Needed for the android tools
|
os: linux
|
||||||
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
|
@ -146,24 +170,24 @@ matrix:
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz | tar -xz
|
- curl https://dl.google.com/go/go1.12.linux-amd64.tar.gz | tar -xz
|
||||||
- export PATH=`pwd`/go/bin:$PATH
|
- export PATH=`pwd`/go/bin:$PATH
|
||||||
- export GOROOT=`pwd`/go
|
- export GOROOT=`pwd`/go
|
||||||
- export GOPATH=$HOME/go
|
- export GOPATH=$HOME/go
|
||||||
script:
|
script:
|
||||||
# Build the Android archive and upload it to Maven Central and Azure
|
# 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
|
- curl https://dl.google.com/android/repository/android-ndk-r19b-linux-x86_64.zip -o android-ndk-r19b.zip
|
||||||
- unzip -q android-ndk-r15c.zip && rm android-ndk-r15c.zip
|
- unzip -q android-ndk-r19b.zip && rm android-ndk-r19b.zip
|
||||||
- mv android-ndk-r15c $HOME
|
- mv android-ndk-r19b $ANDROID_HOME/ndk-bundle
|
||||||
- export ANDROID_NDK=$HOME/android-ndk-r15c
|
|
||||||
|
|
||||||
- mkdir -p $GOPATH/src/github.com/ethereum
|
- mkdir -p $GOPATH/src/github.com/ethereum
|
||||||
- ln -s `pwd` $GOPATH/src/github.com/ethereum
|
- ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum
|
||||||
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
|
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
||||||
- os: osx
|
- if: repo = ethereum/go-ethereum AND type = push
|
||||||
go: 1.9.x
|
os: osx
|
||||||
|
go: 1.12.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- azure-ios
|
- azure-ios
|
||||||
|
|
@ -185,13 +209,15 @@ matrix:
|
||||||
- xctool -version
|
- xctool -version
|
||||||
- xcrun simctl list
|
- xcrun simctl list
|
||||||
|
|
||||||
|
# Workaround for https://github.com/golang/go/issues/23749
|
||||||
|
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
|
||||||
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
||||||
|
|
||||||
# This builder does the Azure archive purges to avoid accumulating junk
|
# This builder does the Azure archive purges to avoid accumulating junk
|
||||||
- os: linux
|
- if: repo = ethereum/go-ethereum AND type = cron
|
||||||
|
os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
go: 1.12.x
|
||||||
go: 1.9.x
|
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
git:
|
git:
|
||||||
|
|
@ -199,9 +225,11 @@ matrix:
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go purge -store gethstore/builds -days 14
|
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||||
|
|
||||||
notifications:
|
- name: Race Detector for Swarm
|
||||||
webhooks:
|
if: repo = ethersphere/go-ethereum
|
||||||
urls:
|
os: linux
|
||||||
- https://webhooks.gitter.im/e/e09ccdce1048c5e03445
|
dist: trusty
|
||||||
on_success: change
|
go: 1.12.x
|
||||||
on_failure: always
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
|
script: ./build/travis_keepalive.sh go test -v -timeout 20m -race ./swarm...
|
||||||
|
|
|
||||||
93
AUTHORS
|
|
@ -1,85 +1,174 @@
|
||||||
# This is the official list of go-ethereum authors for copyright purposes.
|
# 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>
|
Ales Katona <ales@coinbase.com>
|
||||||
Alex Leverington <alex@ethdev.com>
|
Alex Leverington <alex@ethdev.com>
|
||||||
|
Alex Wu <wuyiding@gmail.com>
|
||||||
Alexandre Van de Sande <alex.vandesande@ethdev.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>
|
Aron Fischer <github@aron.guru>
|
||||||
Bas van Kervel <bas@ethdev.com>
|
Bas van Kervel <bas@ethdev.com>
|
||||||
Benjamin Brent <benjamin@benjaminbrent.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>
|
Brian Schroeder <bts@gmail.com>
|
||||||
Casey Detrio <cdetrio@gmail.com>
|
Casey Detrio <cdetrio@gmail.com>
|
||||||
|
Chase Wright <mysticryuujin@gmail.com>
|
||||||
Christoph Jentzsch <jentzsch.software@gmail.com>
|
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||||
Daniel A. Nagy <nagy.da@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>
|
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>
|
Elliot Shepherd <elliot@identitii.com>
|
||||||
Enrique Fynn <enriquefynn@gmail.com>
|
Enrique Fynn <enriquefynn@gmail.com>
|
||||||
|
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||||
Ethan Buchman <ethan@coinculture.info>
|
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>
|
Fabian Vogelsteller <fabian@frozeman.de>
|
||||||
|
Fabio Barone <fabio.barone.co@gmail.com>
|
||||||
Fabio Berger <fabioberger1991@gmail.com>
|
Fabio Berger <fabioberger1991@gmail.com>
|
||||||
|
FaceHo <facehoshi@gmail.com>
|
||||||
Felix Lange <fjl@twurst.com>
|
Felix Lange <fjl@twurst.com>
|
||||||
|
Fiisio <liangcszzu@163.com>
|
||||||
Frank Wang <eternnoir@gmail.com>
|
Frank Wang <eternnoir@gmail.com>
|
||||||
|
Furkan KAMACI <furkankamaci@gmail.com>
|
||||||
Gary Rong <garyrong0905@gmail.com>
|
Gary Rong <garyrong0905@gmail.com>
|
||||||
|
George Ornbo <george@shapeshed.com>
|
||||||
Gregg Dourgarian <greggd@tempworks.com>
|
Gregg Dourgarian <greggd@tempworks.com>
|
||||||
|
Guillaume Ballet <gballet@gmail.com>
|
||||||
Guillaume Nicolas <guin56@gmail.com>
|
Guillaume Nicolas <guin56@gmail.com>
|
||||||
Gustav Simonsson <gustav.simonsson@gmail.com>
|
Gustav Simonsson <gustav.simonsson@gmail.com>
|
||||||
Hao Bryan Cheng <haobcheng@gmail.com>
|
Hao Bryan Cheng <haobcheng@gmail.com>
|
||||||
Henning Diedrich <hd@eonblast.com>
|
Henning Diedrich <hd@eonblast.com>
|
||||||
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
|
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
|
||||||
|
Ivan Daniluk <ivan.daniluk@gmail.com>
|
||||||
Jae Kwon <jkwon.work@gmail.com>
|
Jae Kwon <jkwon.work@gmail.com>
|
||||||
Jamie Pitts <james.pitts@gmail.com>
|
Jamie Pitts <james.pitts@gmail.com>
|
||||||
|
Janoš Guljaš <janos@users.noreply.github.com>
|
||||||
Jason Carver <jacarver@linkedin.com>
|
Jason Carver <jacarver@linkedin.com>
|
||||||
|
Jay Guo <guojiannan1101@gmail.com>
|
||||||
Jeff R. Allen <jra@nella.org>
|
Jeff R. Allen <jra@nella.org>
|
||||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||||
Jens Agerberg <github@agerberg.me>
|
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>
|
Jonathan Brown <jbrown@bluedroplet.com>
|
||||||
Joseph Chow <ethereum@outlook.com>
|
Joseph Chow <ethereum@outlook.com>
|
||||||
Justin Clark-Casey <justincc@justincc.org>
|
Justin Clark-Casey <justincc@justincc.org>
|
||||||
Justin Drake <drakefjustin@gmail.com>
|
Justin Drake <drakefjustin@gmail.com>
|
||||||
Kenji Siu <kenji@isuntv.com>
|
Kenji Siu <kenji@isuntv.com>
|
||||||
Kobi Gurkan <kobigurk@gmail.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>
|
Lefteris Karapetsas <lefteris@refu.co>
|
||||||
Leif Jurvetson <leijurv@gmail.com>
|
Leif Jurvetson <leijurv@gmail.com>
|
||||||
|
Leo Shklovskii <leo@thermopylae.net>
|
||||||
Lewis Marshall <lewis@lmars.net>
|
Lewis Marshall <lewis@lmars.net>
|
||||||
|
Lio李欧 <lionello@users.noreply.github.com>
|
||||||
Louis Holbrook <dev@holbrook.no>
|
Louis Holbrook <dev@holbrook.no>
|
||||||
Luca Zeug <luclu@users.noreply.github.com>
|
Luca Zeug <luclu@users.noreply.github.com>
|
||||||
|
Magicking <s@6120.eu>
|
||||||
Maran Hidskes <maran.hidskes@gmail.com>
|
Maran Hidskes <maran.hidskes@gmail.com>
|
||||||
Marek Kotewicz <marek.kotewicz@gmail.com>
|
Marek Kotewicz <marek.kotewicz@gmail.com>
|
||||||
|
Mark <markya0616@gmail.com>
|
||||||
Martin Holst Swende <martin@swende.se>
|
Martin Holst Swende <martin@swende.se>
|
||||||
Matthew Di Ferrante <mattdf@users.noreply.github.com>
|
Matthew Di Ferrante <mattdf@users.noreply.github.com>
|
||||||
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
|
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
|
||||||
|
Maximilian Meister <mmeister@suse.de>
|
||||||
Micah Zoltu <micah@zoltu.net>
|
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>
|
Nchinda Nchinda <nchinda2@gmail.com>
|
||||||
Nick Dodson <silentcicero@outlook.com>
|
Nick Dodson <silentcicero@outlook.com>
|
||||||
Nick Johnson <arachnid@notdot.net>
|
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>
|
Paulo L F Casaretto <pcasaretto@gmail.com>
|
||||||
|
Paweł Bylica <chfast@gmail.com>
|
||||||
Peter Pratscher <pratscher@gmail.com>
|
Peter Pratscher <pratscher@gmail.com>
|
||||||
|
Petr Mikusek <petr@mikusek.info>
|
||||||
Péter Szilágyi <peterke@gmail.com>
|
Péter Szilágyi <peterke@gmail.com>
|
||||||
RJ Catalano <rj@erisindustries.com>
|
RJ Catalano <catalanor0220@gmail.com>
|
||||||
Ramesh Nair <ram@hiddentao.com>
|
Ramesh Nair <ram@hiddentao.com>
|
||||||
Ricardo Catalinas Jiménez <r@untroubled.be>
|
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>
|
Rémy Roy <remyroy@remyroy.com>
|
||||||
|
S. Matthew English <s-matthew-english@users.noreply.github.com>
|
||||||
Shintaro Kaneko <kaneshin0120@gmail.com>
|
Shintaro Kaneko <kaneshin0120@gmail.com>
|
||||||
|
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||||
Stein Dekker <dekker.stein@gmail.com>
|
Stein Dekker <dekker.stein@gmail.com>
|
||||||
|
Steve Waldman <swaldman@mchange.com>
|
||||||
Steven Roose <stevenroose@gmail.com>
|
Steven Roose <stevenroose@gmail.com>
|
||||||
Taylor Gerring <taylor.gerring@gmail.com>
|
Taylor Gerring <taylor.gerring@gmail.com>
|
||||||
Thomas Bocek <tom@tomp2p.net>
|
Thomas Bocek <tom@tomp2p.net>
|
||||||
|
Ti Zhou <tizhou1986@gmail.com>
|
||||||
Tosh Camille <tochecamille@gmail.com>
|
Tosh Camille <tochecamille@gmail.com>
|
||||||
Valentin Wüstholz <wuestholz@users.noreply.github.com>
|
Valentin Wüstholz <wuestholz@gmail.com>
|
||||||
Victor Farazdagi <simple.square@gmail.com>
|
Victor Farazdagi <simple.square@gmail.com>
|
||||||
Victor Tran <vu.tran54@gmail.com>
|
Victor Tran <vu.tran54@gmail.com>
|
||||||
Viktor Trón <viktor.tron@gmail.com>
|
Viktor Trón <viktor.tron@gmail.com>
|
||||||
Ville Sundell <github@solarius.fi>
|
Ville Sundell <github@solarius.fi>
|
||||||
Vincent G <caktux@gmail.com>
|
Vincent G <caktux@gmail.com>
|
||||||
Vitalik Buterin <v@buterin.com>
|
Vitalik Buterin <v@buterin.com>
|
||||||
|
Vitaly V <vvelikodny@gmail.com>
|
||||||
Vivek Anand <vivekanand1101@users.noreply.github.com>
|
Vivek Anand <vivekanand1101@users.noreply.github.com>
|
||||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||||
Yohann Léon <sybiload@gmail.com>
|
Yohann Léon <sybiload@gmail.com>
|
||||||
Yoichi Hirai <i@yoichihirai.com>
|
Yoichi Hirai <i@yoichihirai.com>
|
||||||
|
Yondon Fu <yondon.fu@gmail.com>
|
||||||
|
Zach <zach.ramsay@gmail.com>
|
||||||
Zahoor Mohamed <zahoor@zahoor.in>
|
Zahoor Mohamed <zahoor@zahoor.in>
|
||||||
|
Zoe Nolan <github@zoenolan.org>
|
||||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
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>
|
holisticode <holistic.computing@gmail.com>
|
||||||
|
jtakalai <juuso.takalainen@streamr.com>
|
||||||
ken10100147 <sunhongping@kanjian.com>
|
ken10100147 <sunhongping@kanjian.com>
|
||||||
ligi <ligi@ligi.de>
|
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>
|
xiekeyang <xiekeyang@users.noreply.github.com>
|
||||||
|
yoza <yoza.is12s@gmail.com>
|
||||||
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||||
|
Ralph Caraveo <deckarep@gmail.com>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.9-alpine as builder
|
FROM golang:1.12-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||||
|
|
||||||
|
|
@ -12,5 +12,5 @@ FROM alpine:latest
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
||||||
|
|
||||||
EXPOSE 8545 8546 30303 30303/udp 30304/udp
|
EXPOSE 8545 8546 30303 30303/udp
|
||||||
ENTRYPOINT ["geth"]
|
ENTRYPOINT ["geth"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Build Geth in a stock Go builder container
|
# Build Geth in a stock Go builder container
|
||||||
FROM golang:1.9-alpine as builder
|
FROM golang:1.12-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||||
|
|
||||||
|
|
@ -12,4 +12,4 @@ FROM alpine:latest
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
||||||
|
|
||||||
EXPOSE 8545 8546 30303 30303/udp 30304/udp
|
EXPOSE 8545 8546 30303 30303/udp
|
||||||
|
|
|
||||||
7
Makefile
|
|
@ -37,7 +37,11 @@ ios:
|
||||||
test: all
|
test: all
|
||||||
build/env.sh go run build/ci.go test
|
build/env.sh go run build/ci.go test
|
||||||
|
|
||||||
|
lint: ## Run linters.
|
||||||
|
build/env.sh go run build/ci.go lint
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
./build/clean_go_build_cache.sh
|
||||||
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
||||||
|
|
||||||
# The devtools target installs tools required for 'go generate'.
|
# The devtools target installs tools required for 'go generate'.
|
||||||
|
|
@ -53,6 +57,9 @@ devtools:
|
||||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||||
|
|
||||||
|
swarm-devtools:
|
||||||
|
env GOBIN= go install ./cmd/swarm/mimegen
|
||||||
|
|
||||||
# Cross Compilation Targets (xgo)
|
# Cross Compilation Targets (xgo)
|
||||||
|
|
||||||
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||||
|
|
|
||||||
67
README.md
|
|
@ -1,11 +1,13 @@
|
||||||
## Go Ethereum
|
## Go Ethereum
|
||||||
|
|
||||||
Official golang implementation of the Ethereum protocol.
|
Official Golang implementation of the Ethereum protocol.
|
||||||
|
|
||||||
[](https://godoc.org/github.com/ethereum/go-ethereum)
|
)](https://godoc.org/github.com/ethereum/go-ethereum)
|
||||||
[](https://gitter.im/ethereum/go-ethereum?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
||||||
|
[](https://travis-ci.org/ethereum/go-ethereum)
|
||||||
|
[](https://discord.gg/nthXNEv)
|
||||||
|
|
||||||
Automated builds are available for stable releases and the unstable master branch.
|
Automated builds are available for stable releases and the unstable master branch.
|
||||||
Binary archives are published at https://geth.ethereum.org/downloads/.
|
Binary archives are published at https://geth.ethereum.org/downloads/.
|
||||||
|
|
@ -16,7 +18,7 @@ For prerequisites and detailed build instructions please read the
|
||||||
[Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum)
|
[Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum)
|
||||||
on the wiki.
|
on the wiki.
|
||||||
|
|
||||||
Building geth requires both a Go (version 1.7 or later) and a C compiler.
|
Building geth requires both a Go (version 1.9 or later) and a C compiler.
|
||||||
You can install them using your favourite package manager.
|
You can install them using your favourite package manager.
|
||||||
Once the dependencies are installed, run
|
Once the dependencies are installed, run
|
||||||
|
|
||||||
|
|
@ -32,13 +34,13 @@ The go-ethereum project comes with several wrappers/executables found in the `cm
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|:----------:|-------------|
|
|:----------:|-------------|
|
||||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default) archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
||||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||||
| `swarm` | swarm daemon and tools. This is the entrypoint for the swarm network. `swarm --help` for command line options and subcommands. See https://swarm-guide.readthedocs.io for swarm documentation. |
|
| `swarm` | Swarm daemon and tools. This is the entry point for the Swarm network. `swarm --help` for command line options and subcommands. See [Swarm README](https://github.com/ethereum/go-ethereum/tree/master/swarm) for more information. |
|
||||||
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
||||||
|
|
||||||
## Running geth
|
## Running geth
|
||||||
|
|
@ -56,23 +58,21 @@ the user doesn't care about years-old historical data, so we can fast-sync quick
|
||||||
state of the network. To do so:
|
state of the network. To do so:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ geth --fast --cache=512 console
|
$ geth console
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will:
|
This command will:
|
||||||
|
|
||||||
* Start geth in fast sync mode (`--fast`), causing it to download more data in exchange for avoiding
|
* Start geth in fast sync mode (default, can be changed with the `--syncmode` flag), causing it to
|
||||||
processing the entire history of the Ethereum network, which is very CPU intensive.
|
download more data in exchange for avoiding processing the entire history of the Ethereum network,
|
||||||
* Bump the memory allowance of the database to 512MB (`--cache=512`), which can help significantly in
|
which is very CPU intensive.
|
||||||
sync times especially for HDD users. This flag is optional and you can set it as high or as low as
|
|
||||||
you'd like, though we'd recommend the 512MB - 2GB range.
|
|
||||||
* Start up Geth's built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console),
|
* Start up Geth's built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console),
|
||||||
(via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API)
|
(via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API)
|
||||||
as well as Geth's own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs).
|
as well as Geth's own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs).
|
||||||
This too is optional and if you leave it out you can always attach to an already running Geth instance
|
This tool is optional and if you leave it out you can always attach to an already running Geth instance
|
||||||
with `geth attach`.
|
with `geth attach`.
|
||||||
|
|
||||||
### Full node on the Ethereum test network
|
### A Full node on the Ethereum test network
|
||||||
|
|
||||||
Transitioning towards developers, if you'd like to play around with creating Ethereum contracts, you
|
Transitioning towards developers, if you'd like to play around with creating Ethereum contracts, you
|
||||||
almost certainly would like to do that without any real money involved until you get the hang of the
|
almost certainly would like to do that without any real money involved until you get the hang of the
|
||||||
|
|
@ -80,14 +80,13 @@ entire system. In other words, instead of attaching to the main network, you wan
|
||||||
network with your node, which is fully equivalent to the main network, but with play-Ether only.
|
network with your node, which is fully equivalent to the main network, but with play-Ether only.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ geth --testnet --fast --cache=512 console
|
$ geth --testnet console
|
||||||
```
|
```
|
||||||
|
|
||||||
The `--fast`, `--cache` flags and `console` subcommand have the exact same meaning as above and they
|
The `console` subcommand has the exact same meaning as above and they are equally useful on the
|
||||||
are equally useful on the testnet too. Please see above for their explanations if you've skipped to
|
testnet too. Please see above for their explanations if you've skipped here.
|
||||||
here.
|
|
||||||
|
|
||||||
Specifying the `--testnet` flag however will reconfigure your Geth instance a bit:
|
Specifying the `--testnet` flag, however, will reconfigure your Geth instance a bit:
|
||||||
|
|
||||||
* Instead of using the default data directory (`~/.ethereum` on Linux for example), Geth will nest
|
* Instead of using the default data directory (`~/.ethereum` on Linux for example), Geth will nest
|
||||||
itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on Linux). Note, on OSX
|
itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on Linux). Note, on OSX
|
||||||
|
|
@ -102,6 +101,14 @@ over between the main network and test network, you should make sure to always u
|
||||||
for play-money and real-money. Unless you manually move accounts, Geth will by default correctly
|
for play-money and real-money. Unless you manually move accounts, Geth will by default correctly
|
||||||
separate the two networks and will not make any accounts available between them.*
|
separate the two networks and will not make any accounts available between them.*
|
||||||
|
|
||||||
|
### Full node on the Rinkeby test network
|
||||||
|
|
||||||
|
The above test network is a cross-client one based on the ethash proof-of-work consensus algorithm. As such, it has certain extra overhead and is more susceptible to reorganization attacks due to the network's low difficulty/security. Go Ethereum also supports connecting to a proof-of-authority based test network called [*Rinkeby*](https://www.rinkeby.io) (operated by members of the community). This network is lighter, more secure, but is only supported by go-ethereum.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ geth --rinkeby console
|
||||||
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a configuration file via:
|
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a configuration file via:
|
||||||
|
|
@ -125,20 +132,20 @@ One of the quickest ways to get Ethereum up and running on your machine is by us
|
||||||
```
|
```
|
||||||
docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
|
docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
|
||||||
-p 8545:8545 -p 30303:30303 \
|
-p 8545:8545 -p 30303:30303 \
|
||||||
ethereum/client-go --fast --cache=512
|
ethereum/client-go
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start geth in fast sync mode with a DB memory allowance of 512MB just as the above command does. It will also create a persistent volume in your home directory for saving your blockchain as well as map the default ports. There is also an `alpine` tag available for a slim version of the image.
|
This will start geth in fast-sync mode with a DB memory allowance of 1GB just as the above command does. It will also create a persistent volume in your home directory for saving your blockchain as well as map the default ports. There is also an `alpine` tag available for a slim version of the image.
|
||||||
|
|
||||||
Do not forget `--rpcaddr 0.0.0.0`, if you want to access RPC from other containers and/or hosts. By default, `geth` binds to the local interface and RPC endpoints is not accessible from the outside.
|
Do not forget `--rpcaddr 0.0.0.0`, if you want to access RPC from other containers and/or hosts. By default, `geth` binds to the local interface and RPC endpoints is not accessible from the outside.
|
||||||
|
|
||||||
### Programatically interfacing Geth nodes
|
### Programmatically interfacing Geth nodes
|
||||||
|
|
||||||
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
||||||
network via your own programs and not manually through the console. To aid this, Geth has built in
|
network via your own programs and not manually through the console. To aid this, Geth has built-in
|
||||||
support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) and
|
support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) and
|
||||||
[Geth specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). These can be
|
[Geth specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). These can be
|
||||||
exposed via HTTP, WebSockets and IPC (unix sockets on unix based platforms, and named pipes on Windows).
|
exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based platforms, and named pipes on Windows).
|
||||||
|
|
||||||
The IPC interface is enabled by default and exposes all the APIs supported by Geth, whereas the HTTP
|
The IPC interface is enabled by default and exposes all the APIs supported by Geth, whereas the HTTP
|
||||||
and WS interfaces need to manually be enabled and only expose a subset of APIs due to security reasons.
|
and WS interfaces need to manually be enabled and only expose a subset of APIs due to security reasons.
|
||||||
|
|
@ -161,12 +168,12 @@ HTTP based JSON-RPC API options:
|
||||||
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
|
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
|
||||||
|
|
||||||
You'll need to use your own programming environments' capabilities (libraries, tools, etc) to connect
|
You'll need to use your own programming environments' capabilities (libraries, tools, etc) to connect
|
||||||
via HTTP, WS or IPC to a Geth node configured with the above flags and you'll need to speak [JSON-RPC](http://www.jsonrpc.org/specification)
|
via HTTP, WS or IPC to a Geth node configured with the above flags and you'll need to speak [JSON-RPC](https://www.jsonrpc.org/specification)
|
||||||
on all transports. You can reuse the same connection for multiple requests!
|
on all transports. You can reuse the same connection for multiple requests!
|
||||||
|
|
||||||
**Note: Please understand the security implications of opening up an HTTP/WS based transport before
|
**Note: Please understand the security implications of opening up an HTTP/WS based transport before
|
||||||
doing so! Hackers on the internet are actively trying to subvert Ethereum nodes with exposed APIs!
|
doing so! Hackers on the internet are actively trying to subvert Ethereum nodes with exposed APIs!
|
||||||
Further, all browser tabs can access locally running webservers, so malicious webpages could try to
|
Further, all browser tabs can access locally running web servers, so malicious web pages could try to
|
||||||
subvert locally available APIs!**
|
subvert locally available APIs!**
|
||||||
|
|
||||||
### Operating a private network
|
### Operating a private network
|
||||||
|
|
@ -234,7 +241,7 @@ that other nodes can use to connect to it and exchange peer information. Make su
|
||||||
displayed IP address information (most probably `[::]`) with your externally accessible IP to get the
|
displayed IP address information (most probably `[::]`) with your externally accessible IP to get the
|
||||||
actual `enode` URL.
|
actual `enode` URL.
|
||||||
|
|
||||||
*Note: You could also use a full fledged Geth node as a bootnode, but it's the less recommended way.*
|
*Note: You could also use a full-fledged Geth node as a bootnode, but it's the less recommended way.*
|
||||||
|
|
||||||
#### Starting up your member nodes
|
#### Starting up your member nodes
|
||||||
|
|
||||||
|
|
@ -257,7 +264,7 @@ an OpenCL or CUDA enabled `ethminer` instance. For information on such a setup,
|
||||||
[EtherMining subreddit](https://www.reddit.com/r/EtherMining/) and the [Genoil miner](https://github.com/Genoil/cpp-ethereum)
|
[EtherMining subreddit](https://www.reddit.com/r/EtherMining/) and the [Genoil miner](https://github.com/Genoil/cpp-ethereum)
|
||||||
repository.
|
repository.
|
||||||
|
|
||||||
In a private network setting however, a single CPU miner instance is more than enough for practical
|
In a private network setting, however a single CPU miner instance is more than enough for practical
|
||||||
purposes as it can produce a stable stream of blocks at the correct intervals without needing heavy
|
purposes as it can produce a stable stream of blocks at the correct intervals without needing heavy
|
||||||
resources (consider running on a single thread, no need for multiple ones either). To start a Geth
|
resources (consider running on a single thread, no need for multiple ones either). To start a Geth
|
||||||
instance for mining, run it with all your usual flags, extended by:
|
instance for mining, run it with all your usual flags, extended by:
|
||||||
|
|
@ -291,7 +298,7 @@ Please make sure your contributions adhere to our coding guidelines:
|
||||||
* E.g. "eth, rpc: make trace configs optional"
|
* E.g. "eth, rpc: make trace configs optional"
|
||||||
|
|
||||||
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||||
for more details on configuring your environment, managing project dependencies and testing procedures.
|
for more details on configuring your environment, managing project dependencies, and testing procedures.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
1
VERSION
|
|
@ -1 +0,0 @@
|
||||||
1.8.0
|
|
||||||
|
|
@ -58,13 +58,11 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return arguments, nil
|
return arguments, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
method, exist := abi.Methods[name]
|
method, exist := abi.Methods[name]
|
||||||
if !exist {
|
if !exist {
|
||||||
return nil, fmt.Errorf("method '%s' not found", name)
|
return nil, fmt.Errorf("method '%s' not found", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments, err := method.Inputs.Pack(args...)
|
arguments, err := method.Inputs.Pack(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -82,7 +80,7 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||||
// we need to decide whether we're calling a method or an event
|
// we need to decide whether we're calling a method or an event
|
||||||
if method, ok := abi.Methods[name]; ok {
|
if method, ok := abi.Methods[name]; ok {
|
||||||
if len(output)%32 != 0 {
|
if len(output)%32 != 0 {
|
||||||
return fmt.Errorf("abi: improperly formatted output")
|
return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(output), output)
|
||||||
}
|
}
|
||||||
return method.Outputs.Unpack(v, output)
|
return method.Outputs.Unpack(v, output)
|
||||||
} else if event, ok := abi.Events[name]; ok {
|
} else if event, ok := abi.Events[name]; ok {
|
||||||
|
|
@ -136,11 +134,14 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// MethodById looks up a method by the 4-byte id
|
// MethodById looks up a method by the 4-byte id
|
||||||
// returns nil if none found
|
// returns nil if none found
|
||||||
func (abi *ABI) MethodById(sigdata []byte) *Method {
|
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
||||||
|
if len(sigdata) < 4 {
|
||||||
|
return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata))
|
||||||
|
}
|
||||||
for _, method := range abi.Methods {
|
for _, method := range abi.Methods {
|
||||||
if bytes.Equal(method.Id(), sigdata[:4]) {
|
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||||
return &method
|
return &method, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
@ -52,11 +51,14 @@ const jsondata2 = `
|
||||||
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
|
{ "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" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
|
||||||
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
|
{ "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[]" } ] }
|
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }
|
||||||
]`
|
]`
|
||||||
|
|
||||||
func TestReader(t *testing.T) {
|
func TestReader(t *testing.T) {
|
||||||
Uint256, _ := NewType("uint256")
|
Uint256, _ := NewType("uint256", nil)
|
||||||
exp := ABI{
|
exp := ABI{
|
||||||
Methods: map[string]Method{
|
Methods: map[string]Method{
|
||||||
"balance": {
|
"balance": {
|
||||||
|
|
@ -177,7 +179,7 @@ func TestTestSlice(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMethodSignature(t *testing.T) {
|
func TestMethodSignature(t *testing.T) {
|
||||||
String, _ := NewType("string")
|
String, _ := NewType("string", nil)
|
||||||
m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
|
m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
|
||||||
exp := "foo(string,string)"
|
exp := "foo(string,string)"
|
||||||
if m.Sig() != exp {
|
if m.Sig() != exp {
|
||||||
|
|
@ -189,12 +191,31 @@ func TestMethodSignature(t *testing.T) {
|
||||||
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
|
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
|
||||||
}
|
}
|
||||||
|
|
||||||
uintt, _ := NewType("uint256")
|
uintt, _ := NewType("uint256", nil)
|
||||||
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
|
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
|
||||||
exp = "foo(uint256)"
|
exp = "foo(uint256)"
|
||||||
if m.Sig() != exp {
|
if m.Sig() != exp {
|
||||||
t.Error("signature mismatch", exp, "!=", m.Sig())
|
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method with tuple arguments
|
||||||
|
s, _ := NewType("tuple", []ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256"},
|
||||||
|
{Name: "b", Type: "int256[]"},
|
||||||
|
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
|
||||||
|
{Name: "x", Type: "int256"},
|
||||||
|
{Name: "y", Type: "int256"},
|
||||||
|
}},
|
||||||
|
{Name: "d", Type: "tuple[2]", Components: []ArgumentMarshaling{
|
||||||
|
{Name: "x", Type: "int256"},
|
||||||
|
{Name: "y", Type: "int256"},
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
m = Method{"foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil}
|
||||||
|
exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)"
|
||||||
|
if m.Sig() != exp {
|
||||||
|
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiPack(t *testing.T) {
|
func TestMultiPack(t *testing.T) {
|
||||||
|
|
@ -564,11 +585,13 @@ func TestBareEvents(t *testing.T) {
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "type" : "event", "name" : "balance" },
|
{ "type" : "event", "name" : "balance" },
|
||||||
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
{ "type" : "event", "name" : "anon", "anonymous" : true},
|
||||||
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }
|
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] },
|
||||||
|
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
|
||||||
]`
|
]`
|
||||||
|
|
||||||
arg0, _ := NewType("uint256")
|
arg0, _ := NewType("uint256", nil)
|
||||||
arg1, _ := NewType("address")
|
arg1, _ := NewType("address", nil)
|
||||||
|
tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
|
||||||
|
|
||||||
expectedEvents := map[string]struct {
|
expectedEvents := map[string]struct {
|
||||||
Anonymous bool
|
Anonymous bool
|
||||||
|
|
@ -580,6 +603,10 @@ func TestBareEvents(t *testing.T) {
|
||||||
{Name: "arg0", Type: arg0, Indexed: false},
|
{Name: "arg0", Type: arg0, Indexed: false},
|
||||||
{Name: "arg1", Type: arg1, Indexed: true},
|
{Name: "arg1", Type: arg1, Indexed: true},
|
||||||
}},
|
}},
|
||||||
|
"tuple": {false, []Argument{
|
||||||
|
{Name: "t", Type: tuple, Indexed: false},
|
||||||
|
{Name: "arg1", Type: arg1, Indexed: true},
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
|
@ -621,14 +648,16 @@ func TestBareEvents(t *testing.T) {
|
||||||
// TestUnpackEvent is based on this contract:
|
// TestUnpackEvent is based on this contract:
|
||||||
// contract T {
|
// contract T {
|
||||||
// event received(address sender, uint amount, bytes memo);
|
// event received(address sender, uint amount, bytes memo);
|
||||||
|
// event receivedAddr(address sender);
|
||||||
// function receive(bytes memo) external payable {
|
// function receive(bytes memo) external payable {
|
||||||
// received(msg.sender, msg.value, memo);
|
// 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:
|
// 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]}
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
func TestUnpackEvent(t *testing.T) {
|
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"}]`
|
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))
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -644,7 +673,7 @@ func TestUnpackEvent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceivedEvent struct {
|
type ReceivedEvent struct {
|
||||||
Address common.Address
|
Sender common.Address
|
||||||
Amount *big.Int
|
Amount *big.Int
|
||||||
Memo []byte
|
Memo []byte
|
||||||
}
|
}
|
||||||
|
|
@ -653,8 +682,15 @@ func TestUnpackEvent(t *testing.T) {
|
||||||
err = abi.Unpack(&ev, "received", data)
|
err = abi.Unpack(&ev, "received", data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
}
|
||||||
t.Logf("len(data): %d; received event: %+v", len(data), ev)
|
|
||||||
|
type ReceivedAddrEvent struct {
|
||||||
|
Sender common.Address
|
||||||
|
}
|
||||||
|
var receivedAddrEv ReceivedAddrEvent
|
||||||
|
err = abi.Unpack(&receivedAddrEv, "receivedAddr", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -689,10 +725,23 @@ func TestABI_MethodById(t *testing.T) {
|
||||||
}
|
}
|
||||||
for name, m := range abi.Methods {
|
for name, m := range abi.Methods {
|
||||||
a := fmt.Sprintf("%v", m)
|
a := fmt.Sprintf("%v", m)
|
||||||
b := fmt.Sprintf("%v", abi.MethodById(m.Id()))
|
m2, err := abi.MethodById(m.Id())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to look up ABI method: %v", err)
|
||||||
|
}
|
||||||
|
b := fmt.Sprintf("%v", m2)
|
||||||
if a != b {
|
if a != b {
|
||||||
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Also test empty
|
||||||
|
if _, err := abi.MethodById([]byte{0x00}); err == nil {
|
||||||
|
t.Errorf("Expected error, too short to decode data")
|
||||||
|
}
|
||||||
|
if _, err := abi.MethodById([]byte{}); err == nil {
|
||||||
|
t.Errorf("Expected error, too short to decode data")
|
||||||
|
}
|
||||||
|
if _, err := abi.MethodById(nil); err == nil {
|
||||||
|
t.Errorf("Expected error, nil is short to decode data")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,24 +33,27 @@ type Argument struct {
|
||||||
|
|
||||||
type Arguments []Argument
|
type Arguments []Argument
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface
|
type ArgumentMarshaling struct {
|
||||||
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
|
||||||
var extarg struct {
|
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
|
Components []ArgumentMarshaling
|
||||||
Indexed bool
|
Indexed bool
|
||||||
}
|
}
|
||||||
err := json.Unmarshal(data, &extarg)
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
|
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
||||||
|
var arg ArgumentMarshaling
|
||||||
|
err := json.Unmarshal(data, &arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("argument json err: %v", err)
|
return fmt.Errorf("argument json err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
argument.Type, err = NewType(extarg.Type)
|
argument.Type, err = NewType(arg.Type, arg.Components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
argument.Name = extarg.Name
|
argument.Name = arg.Name
|
||||||
argument.Indexed = extarg.Indexed
|
argument.Indexed = arg.Indexed
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +70,17 @@ func (arguments Arguments) LengthNonIndexed() int {
|
||||||
return 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[]
|
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
||||||
func (arguments Arguments) isTuple() bool {
|
func (arguments Arguments) isTuple() bool {
|
||||||
return len(arguments) > 1
|
return len(arguments) > 1
|
||||||
|
|
@ -74,88 +88,145 @@ func (arguments Arguments) isTuple() bool {
|
||||||
|
|
||||||
// Unpack performs the operation hexdata -> Go format
|
// Unpack performs the operation hexdata -> Go format
|
||||||
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
||||||
if arguments.isTuple() {
|
|
||||||
return arguments.unpackTuple(v, data)
|
|
||||||
}
|
|
||||||
return arguments.unpackAtomic(v, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
|
||||||
// make sure the passed value is arguments pointer
|
// make sure the passed value is arguments pointer
|
||||||
valueOf := reflect.ValueOf(v)
|
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
}
|
}
|
||||||
|
marshalledValues, err := arguments.UnpackValues(data)
|
||||||
var (
|
|
||||||
value = valueOf.Elem()
|
|
||||||
typ = value.Type()
|
|
||||||
kind = value.Kind()
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// If the output interface is a struct, make sure names don't collide
|
|
||||||
if kind == reflect.Struct {
|
|
||||||
exists := make(map[string]bool)
|
|
||||||
for _, arg := range arguments {
|
|
||||||
field := capitalise(arg.Name)
|
|
||||||
if field == "" {
|
|
||||||
return fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
|
||||||
}
|
|
||||||
if exists[field] {
|
|
||||||
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
|
|
||||||
}
|
|
||||||
exists[field] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// `i` counts the nonindexed arguments.
|
|
||||||
// `j` counts the number of complex types.
|
|
||||||
// both `i` and `j` are used to to correctly compute `data` offset.
|
|
||||||
|
|
||||||
i, j := -1, 0
|
|
||||||
for _, arg := range arguments {
|
|
||||||
|
|
||||||
if arg.Indexed {
|
|
||||||
// can't read, continue
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if arguments.isTuple() {
|
||||||
|
return arguments.unpackTuple(v, marshalledValues)
|
||||||
|
}
|
||||||
|
return arguments.unpackAtomic(v, marshalledValues[0])
|
||||||
|
}
|
||||||
|
|
||||||
if arg.Type.T == ArrayTy {
|
// unpack sets the unmarshalled value to go format.
|
||||||
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
|
// Note the dst here must be settable.
|
||||||
// we need to decrement 'j' because 'i' was incremented
|
func unpack(t *Type, dst interface{}, src interface{}) error {
|
||||||
j += arg.Type.Size - 1
|
var (
|
||||||
|
dstVal = reflect.ValueOf(dst).Elem()
|
||||||
|
srcVal = reflect.ValueOf(src)
|
||||||
|
)
|
||||||
|
|
||||||
|
if t.T != TupleTy && !((t.T == SliceTy || t.T == ArrayTy) && t.Elem.T == TupleTy) {
|
||||||
|
return set(dstVal, srcVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
switch t.T {
|
||||||
|
case TupleTy:
|
||||||
switch kind {
|
if dstVal.Kind() != reflect.Struct {
|
||||||
case reflect.Struct:
|
return fmt.Errorf("abi: invalid dst value for unpack, want struct, got %s", dstVal.Kind())
|
||||||
name := capitalise(arg.Name)
|
}
|
||||||
for j := 0; j < typ.NumField(); j++ {
|
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, dstVal)
|
||||||
// TODO read tags: `abi:"fieldName"`
|
if err != nil {
|
||||||
if typ.Field(j).Name == name {
|
return err
|
||||||
if err := set(value.Field(j), reflectValue, arg); err != nil {
|
}
|
||||||
|
for i, elem := range t.TupleElems {
|
||||||
|
fname := fieldmap[t.TupleRawNames[i]]
|
||||||
|
field := dstVal.FieldByName(fname)
|
||||||
|
if !field.IsValid() {
|
||||||
|
return fmt.Errorf("abi: field %s can't found in the given value", t.TupleRawNames[i])
|
||||||
|
}
|
||||||
|
if err := unpack(elem, field.Addr().Interface(), srcVal.Field(i).Interface()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
case SliceTy:
|
||||||
|
if dstVal.Kind() != reflect.Slice {
|
||||||
|
return fmt.Errorf("abi: invalid dst value for unpack, want slice, got %s", dstVal.Kind())
|
||||||
|
}
|
||||||
|
slice := reflect.MakeSlice(dstVal.Type(), srcVal.Len(), srcVal.Len())
|
||||||
|
for i := 0; i < slice.Len(); i++ {
|
||||||
|
if err := unpack(t.Elem, slice.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dstVal.Set(slice)
|
||||||
|
case ArrayTy:
|
||||||
|
if dstVal.Kind() != reflect.Array {
|
||||||
|
return fmt.Errorf("abi: invalid dst value for unpack, want array, got %s", dstVal.Kind())
|
||||||
|
}
|
||||||
|
array := reflect.New(dstVal.Type()).Elem()
|
||||||
|
for i := 0; i < array.Len(); i++ {
|
||||||
|
if err := unpack(t.Elem, array.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dstVal.Set(array)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
||||||
|
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error {
|
||||||
|
if arguments.LengthNonIndexed() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
argument := arguments.NonIndexed()[0]
|
||||||
|
elem := reflect.ValueOf(v).Elem()
|
||||||
|
|
||||||
|
if elem.Kind() == reflect.Struct {
|
||||||
|
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
field := elem.FieldByName(fieldmap[argument.Name])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return fmt.Errorf("abi: field %s can't be found in the given value", argument.Name)
|
||||||
|
}
|
||||||
|
return unpack(&argument.Type, field.Addr().Interface(), marshalledValues)
|
||||||
|
}
|
||||||
|
return unpack(&argument.Type, elem.Addr().Interface(), marshalledValues)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackTuple unpacks ( hexdata -> go ) a batch of values.
|
||||||
|
func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error {
|
||||||
|
var (
|
||||||
|
value = reflect.ValueOf(v).Elem()
|
||||||
|
typ = value.Type()
|
||||||
|
kind = value.Kind()
|
||||||
|
)
|
||||||
|
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the interface is a struct, get of abi->struct_field mapping
|
||||||
|
var abi2struct map[string]string
|
||||||
|
if kind == reflect.Struct {
|
||||||
|
var (
|
||||||
|
argNames []string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, arg := range arguments.NonIndexed() {
|
||||||
|
argNames = append(argNames, arg.Name)
|
||||||
|
}
|
||||||
|
abi2struct, err = mapArgNamesToStructFields(argNames, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, arg := range arguments.NonIndexed() {
|
||||||
|
switch kind {
|
||||||
|
case reflect.Struct:
|
||||||
|
field := value.FieldByName(abi2struct[arg.Name])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name)
|
||||||
|
}
|
||||||
|
if err := unpack(&arg.Type, field.Addr().Interface(), marshalledValues[i]); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
if value.Len() < i {
|
if value.Len() < i {
|
||||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||||
}
|
}
|
||||||
v := value.Index(i)
|
v := value.Index(i)
|
||||||
if err := requireAssignable(v, reflectValue); err != nil {
|
if err := requireAssignable(v, reflect.ValueOf(marshalledValues[i])); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := unpack(&arg.Type, v.Addr().Interface(), marshalledValues[i]); err != nil {
|
||||||
if err := set(v.Elem(), reflectValue, arg); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -163,37 +234,55 @@ func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
|
||||||
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
|
// without supplying a struct to unpack into. Instead, this method returns a list containing the
|
||||||
// make sure the passed value is arguments pointer
|
// values. An atomic argument will be a list with one element.
|
||||||
valueOf := reflect.ValueOf(v)
|
func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
retval := make([]interface{}, 0, arguments.LengthNonIndexed())
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
virtualArgs := 0
|
||||||
|
for index, arg := range arguments.NonIndexed() {
|
||||||
|
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
|
||||||
|
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
|
||||||
|
// If we have a static array, like [3]uint256, these are coded as
|
||||||
|
// just like uint256,uint256,uint256.
|
||||||
|
// 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 += getTypeSize(arg.Type)/32 - 1
|
||||||
|
} else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) {
|
||||||
|
// If we have a static tuple, like (uint256, bool, uint256), these are
|
||||||
|
// coded as just like uint256,bool,uint256
|
||||||
|
virtualArgs += getTypeSize(arg.Type)/32 - 1
|
||||||
}
|
}
|
||||||
arg := arguments[0]
|
|
||||||
if arg.Indexed {
|
|
||||||
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
|
||||||
}
|
|
||||||
|
|
||||||
value := valueOf.Elem()
|
|
||||||
|
|
||||||
marshalledValue, err := toGoType(0, arg.Type, output)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
return set(value, reflect.ValueOf(marshalledValue), arg)
|
retval = append(retval, marshalledValue)
|
||||||
|
}
|
||||||
|
return retval, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack performs the operation Go format -> Hexdata
|
// 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) {
|
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
// Make sure arguments match up and pack them
|
// Make sure arguments match up and pack them
|
||||||
abiArgs := arguments
|
abiArgs := arguments
|
||||||
if len(args) != len(abiArgs) {
|
if len(args) != len(abiArgs) {
|
||||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
||||||
}
|
}
|
||||||
|
|
||||||
// variable input is the output appended at the end of packed
|
// variable input is the output appended at the end of packed
|
||||||
// output. This is used for strings and bytes types input.
|
// output. This is used for strings and bytes types input.
|
||||||
var variableInput []byte
|
var variableInput []byte
|
||||||
|
|
@ -201,13 +290,8 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
// input offset is the bytes offset for packed output
|
// input offset is the bytes offset for packed output
|
||||||
inputOffset := 0
|
inputOffset := 0
|
||||||
for _, abiArg := range abiArgs {
|
for _, abiArg := range abiArgs {
|
||||||
if abiArg.Type.T == ArrayTy {
|
inputOffset += getTypeSize(abiArg.Type)
|
||||||
inputOffset += 32 * abiArg.Type.Size
|
|
||||||
} else {
|
|
||||||
inputOffset += 32
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for i, a := range args {
|
for i, a := range args {
|
||||||
input := abiArgs[i]
|
input := abiArgs[i]
|
||||||
|
|
@ -216,15 +300,13 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// check for dynamic types
|
||||||
// check for a slice type (string, bytes, slice)
|
if isDynamicType(input.Type) {
|
||||||
if input.Type.requiresLengthPrefix() {
|
|
||||||
// calculate the offset
|
|
||||||
offset := inputOffset + len(variableInput)
|
|
||||||
// set the offset
|
// set the offset
|
||||||
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
ret = append(ret, packNum(reflect.ValueOf(inputOffset))...)
|
||||||
// Append the packed output to the variable input. The variable input
|
// calculate next offset
|
||||||
// will be appended at the end of the input.
|
inputOffset += len(packed)
|
||||||
|
// append to variable input
|
||||||
variableInput = append(variableInput, packed...)
|
variableInput = append(variableInput, packed...)
|
||||||
} else {
|
} else {
|
||||||
// append the packed value to the input
|
// append the packed value to the input
|
||||||
|
|
@ -237,14 +319,13 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// capitalise makes the first character of a string upper case, also removing any
|
// ToCamelCase converts an under-score string to a camel-case string
|
||||||
// prefixing underscores from the variable names.
|
func ToCamelCase(input string) string {
|
||||||
func capitalise(input string) string {
|
parts := strings.Split(input, "_")
|
||||||
for len(input) > 0 && input[0] == '_' {
|
for i, s := range parts {
|
||||||
input = input[1:]
|
if len(s) > 0 {
|
||||||
|
parts[i] = strings.ToUpper(s[:1]) + s[1:]
|
||||||
}
|
}
|
||||||
if len(input) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
return strings.ToUpper(input[:1]) + input[1:]
|
return strings.Join(parts, "")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"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/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
@ -64,11 +65,11 @@ type SimulatedBackend struct {
|
||||||
|
|
||||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||||
// for testing purposes.
|
// for testing purposes.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||||
database, _ := ethdb.NewMemDatabase()
|
database := ethdb.NewMemDatabase()
|
||||||
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
|
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
|
||||||
genesis.MustCommit(database)
|
genesis.MustCommit(database)
|
||||||
blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil)
|
||||||
|
|
||||||
backend := &SimulatedBackend{
|
backend := &SimulatedBackend{
|
||||||
database: database,
|
database: database,
|
||||||
|
|
@ -102,8 +103,10 @@ func (b *SimulatedBackend) Rollback() {
|
||||||
|
|
||||||
func (b *SimulatedBackend) rollback() {
|
func (b *SimulatedBackend) rollback() {
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
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.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
// CodeAt returns the code associated with a certain account in the blockchain.
|
||||||
|
|
@ -157,10 +160,29 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
|
||||||
|
|
||||||
// TransactionReceipt returns the receipt of a transaction.
|
// TransactionReceipt returns the receipt of a transaction.
|
||||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||||
receipt, _, _, _ := core.GetReceipt(b.database, txHash)
|
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash)
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionByHash checks the pool of pending transactions in addition to the
|
||||||
|
// blockchain. The isPending return value indicates whether the transaction has been
|
||||||
|
// mined yet. Note that the transaction may not be part of the canonical chain even if
|
||||||
|
// it's not pending.
|
||||||
|
func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
|
tx := b.pendingBlock.Transaction(txHash)
|
||||||
|
if tx != nil {
|
||||||
|
return tx, true, nil
|
||||||
|
}
|
||||||
|
tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash)
|
||||||
|
if tx != nil {
|
||||||
|
return tx, false, nil
|
||||||
|
}
|
||||||
|
return nil, false, ethereum.NotFound
|
||||||
|
}
|
||||||
|
|
||||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
// PendingCodeAt returns the code associated with an account in the pending state.
|
||||||
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
|
@ -205,7 +227,7 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||||
// chain doens't have miners, we just return a gas price of 1 for any call.
|
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
||||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||||
return big.NewInt(1), nil
|
return big.NewInt(1), nil
|
||||||
}
|
}
|
||||||
|
|
@ -305,12 +327,14 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
|
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
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() {
|
for _, tx := range b.pendingBlock.Transactions() {
|
||||||
block.AddTx(tx)
|
block.AddTxWithChain(b.blockchain, tx)
|
||||||
}
|
}
|
||||||
block.AddTx(tx)
|
block.AddTxWithChain(b.blockchain, tx)
|
||||||
})
|
})
|
||||||
|
statedb, _ := b.blockchain.State()
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,6 +343,11 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
//
|
//
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
// 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) {
|
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
||||||
|
var filter *filters.Filter
|
||||||
|
if query.BlockHash != nil {
|
||||||
|
// Block filter requested, construct a single-shot filter
|
||||||
|
filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics)
|
||||||
|
} else {
|
||||||
// Initialize unset filter boundaried to run from genesis to chain head
|
// Initialize unset filter boundaried to run from genesis to chain head
|
||||||
from := int64(0)
|
from := int64(0)
|
||||||
if query.FromBlock != nil {
|
if query.FromBlock != nil {
|
||||||
|
|
@ -328,9 +357,10 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter
|
||||||
if query.ToBlock != nil {
|
if query.ToBlock != nil {
|
||||||
to = query.ToBlock.Int64()
|
to = query.ToBlock.Int64()
|
||||||
}
|
}
|
||||||
// Construct and execute the filter
|
// Construct the range filter
|
||||||
filter := filters.New(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
|
filter = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
|
||||||
|
}
|
||||||
|
// Run the filter and return all the logs
|
||||||
logs, err := filter.Logs(ctx)
|
logs, err := filter.Logs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -386,8 +416,10 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||||
}
|
}
|
||||||
block.OffsetTime(int64(adjustment.Seconds()))
|
block.OffsetTime(int64(adjustment.Seconds()))
|
||||||
})
|
})
|
||||||
|
statedb, _ := b.blockchain.State()
|
||||||
|
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -422,11 +454,36 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb
|
||||||
}
|
}
|
||||||
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||||
}
|
}
|
||||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
|
||||||
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
|
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
return fb.bc.GetHeaderByHash(hash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
|
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 {
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
<-quit
|
<-quit
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
82
accounts/abi/bind/backends/simulated_test.go
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package backends_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimulatedBackend(t *testing.T) {
|
||||||
|
var gasLimit uint64 = 8000029
|
||||||
|
key, _ := crypto.GenerateKey() // nolint: gosec
|
||||||
|
auth := bind.NewKeyedTransactor(key)
|
||||||
|
genAlloc := make(core.GenesisAlloc)
|
||||||
|
genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)}
|
||||||
|
|
||||||
|
sim := backends.NewSimulatedBackend(genAlloc, gasLimit)
|
||||||
|
|
||||||
|
// should return an error if the tx is not found
|
||||||
|
txHash := common.HexToHash("2")
|
||||||
|
_, isPending, err := sim.TransactionByHash(context.Background(), txHash)
|
||||||
|
|
||||||
|
if isPending {
|
||||||
|
t.Fatal("transaction should not be pending")
|
||||||
|
}
|
||||||
|
if err != ethereum.NotFound {
|
||||||
|
t.Fatalf("err should be `ethereum.NotFound` but received %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a transaction and confirm you can retrieve it
|
||||||
|
code := `6060604052600a8060106000396000f360606040526008565b00`
|
||||||
|
var gas uint64 = 3000000
|
||||||
|
tx := types.NewContractCreation(0, big.NewInt(0), gas, big.NewInt(1), common.FromHex(code))
|
||||||
|
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key)
|
||||||
|
|
||||||
|
err = sim.SendTransaction(context.Background(), tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error sending transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash = tx.Hash()
|
||||||
|
_, isPending, err = sim.TransactionByHash(context.Background(), txHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting transaction with hash: %v", txHash.String())
|
||||||
|
}
|
||||||
|
if !isPending {
|
||||||
|
t.Fatal("transaction should have pending status")
|
||||||
|
}
|
||||||
|
|
||||||
|
sim.Commit()
|
||||||
|
tx, isPending, err = sim.TransactionByHash(context.Background(), txHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting transaction with hash: %v", txHash.String())
|
||||||
|
}
|
||||||
|
if isPending {
|
||||||
|
t.Fatal("transaction should not have pending status")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Tra
|
||||||
type CallOpts struct {
|
type CallOpts struct {
|
||||||
Pending bool // Whether to operate on the pending state or the last known one
|
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
|
From common.Address // Optional the sender address, otherwise the first account is used
|
||||||
|
BlockNumber *big.Int // Optional the block number on which the call should be performed
|
||||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,10 +148,10 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output, err = c.caller.CallContract(ctx, msg, nil)
|
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
|
||||||
if err == nil && len(output) == 0 {
|
if err == nil && len(output) == 0 {
|
||||||
// Make sure we have a contract to operate on, and bail out otherwise.
|
// Make sure we have a contract to operate on, and bail out otherwise.
|
||||||
if code, err = c.caller.CodeAt(ctx, c.address, nil); err != nil {
|
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if len(code) == 0 {
|
} else if len(code) == 0 {
|
||||||
return ErrNoCode
|
return ErrNoCode
|
||||||
|
|
|
||||||
80
accounts/abi/bind/base_test.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package bind_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockCaller struct {
|
||||||
|
codeAtBlockNumber *big.Int
|
||||||
|
callContractBlockNumber *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
mc.codeAtBlockNumber = blockNumber
|
||||||
|
return []byte{1, 2, 3}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
mc.callContractBlockNumber = blockNumber
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
|
||||||
|
mc := &mockCaller{}
|
||||||
|
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
||||||
|
Methods: map[string]abi.Method{
|
||||||
|
"something": {
|
||||||
|
Name: "something",
|
||||||
|
Outputs: abi.Arguments{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, mc, nil, nil)
|
||||||
|
var ret string
|
||||||
|
|
||||||
|
blockNumber := big.NewInt(42)
|
||||||
|
|
||||||
|
bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something")
|
||||||
|
|
||||||
|
if mc.callContractBlockNumber != blockNumber {
|
||||||
|
t.Fatalf("CallContract() was not passed the block number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mc.codeAtBlockNumber != blockNumber {
|
||||||
|
t.Fatalf("CodeAt() was not passed the block number")
|
||||||
|
}
|
||||||
|
|
||||||
|
bc.Call(&bind.CallOpts{}, &ret, "something")
|
||||||
|
|
||||||
|
if mc.callContractBlockNumber != nil {
|
||||||
|
t.Fatalf("CallContract() was passed a block number when it should not have been")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mc.codeAtBlockNumber != nil {
|
||||||
|
t.Fatalf("CodeAt() was passed a block number when it should not have been")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,13 +23,13 @@ package bind
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/format"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"golang.org/x/tools/imports"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lang is a target programming language selector to generate bindings for.
|
// Lang is a target programming language selector to generate bindings for.
|
||||||
|
|
@ -145,9 +145,9 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
if err := tmpl.Execute(buffer, data); err != nil {
|
if err := tmpl.Execute(buffer, data); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// For Go bindings pass the code through goimports to clean it up and double check
|
// For Go bindings pass the code through gofmt to clean it up
|
||||||
if lang == LangGo {
|
if lang == LangGo {
|
||||||
code, err := imports.Process(".", buffer.Bytes(), nil)
|
code, err := format.Source(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("%v\n%s", err, buffer)
|
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||||
}
|
}
|
||||||
|
|
@ -164,118 +164,147 @@ var bindType = map[Lang]func(kind abi.Type) string{
|
||||||
LangJava: bindTypeJava,
|
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
|
// 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
|
// 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).
|
// mapped will use an upscaled type (e.g. *big.Int).
|
||||||
func bindTypeGo(kind abi.Type) string {
|
func bindTypeGo(kind abi.Type) string {
|
||||||
stringKind := kind.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 translated type.
|
||||||
|
func bindUnnestedTypeGo(stringKind string) (int, string) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(stringKind, "address"):
|
case strings.HasPrefix(stringKind, "address"):
|
||||||
parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
return len("address"), "common.Address"
|
||||||
if len(parts) != 2 {
|
|
||||||
return stringKind
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%scommon.Address", parts[1])
|
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "bytes"):
|
case strings.HasPrefix(stringKind, "bytes"):
|
||||||
parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind)
|
||||||
if len(parts) != 3 {
|
return len(parts[0]), fmt.Sprintf("[%s]byte", parts[1])
|
||||||
return stringKind
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s[%s]byte", parts[2], parts[1])
|
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
|
case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
|
||||||
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind)
|
||||||
if len(parts) != 4 {
|
|
||||||
return stringKind
|
|
||||||
}
|
|
||||||
switch parts[2] {
|
switch parts[2] {
|
||||||
case "8", "16", "32", "64":
|
case "8", "16", "32", "64":
|
||||||
return fmt.Sprintf("%s%sint%s", parts[3], parts[1], parts[2])
|
return len(parts[0]), fmt.Sprintf("%sint%s", parts[1], parts[2])
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s*big.Int", parts[3])
|
return len(parts[0]), "*big.Int"
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "bool") || strings.HasPrefix(stringKind, "string"):
|
case strings.HasPrefix(stringKind, "bool"):
|
||||||
parts := regexp.MustCompile(`([a-z]+)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
return len("bool"), "bool"
|
||||||
if len(parts) != 3 {
|
|
||||||
return stringKind
|
case strings.HasPrefix(stringKind, "string"):
|
||||||
}
|
return len("string"), "string"
|
||||||
return fmt.Sprintf("%s%s", parts[2], parts[1])
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return stringKind
|
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
|
// 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
|
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
|
||||||
// mapped will use an upscaled type (e.g. BigDecimal).
|
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||||
func bindTypeJava(kind abi.Type) string {
|
func bindTypeJava(kind abi.Type) string {
|
||||||
stringKind := kind.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 translated type.
|
||||||
|
func bindUnnestedTypeJava(stringKind string) (int, string) {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasPrefix(stringKind, "address"):
|
case strings.HasPrefix(stringKind, "address"):
|
||||||
parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
parts := regexp.MustCompile(`address(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return stringKind
|
return len(stringKind), stringKind
|
||||||
}
|
}
|
||||||
if parts[1] == "" {
|
if parts[1] == "" {
|
||||||
return fmt.Sprintf("Address")
|
return len("address"), "Address"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("Addresses")
|
return len(parts[0]), "Addresses"
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "bytes"):
|
case strings.HasPrefix(stringKind, "bytes"):
|
||||||
parts := regexp.MustCompile(`bytes([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
parts := regexp.MustCompile(`bytes([0-9]*)`).FindStringSubmatch(stringKind)
|
||||||
if len(parts) != 3 {
|
if len(parts) != 2 {
|
||||||
return stringKind
|
return len(stringKind), stringKind
|
||||||
}
|
}
|
||||||
if parts[2] != "" {
|
return len(parts[0]), "byte[]"
|
||||||
return "byte[][]"
|
|
||||||
}
|
|
||||||
return "byte[]"
|
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
|
case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"):
|
||||||
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
//Note that uint and int (without digits) are also matched,
|
||||||
if len(parts) != 4 {
|
// these are size 256, and will translate to BigInt (the default).
|
||||||
return stringKind
|
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(stringKind)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return len(stringKind), stringKind
|
||||||
}
|
}
|
||||||
switch parts[2] {
|
|
||||||
case "8", "16", "32", "64":
|
namedSize := map[string]string{
|
||||||
if parts[1] == "" {
|
"8": "byte",
|
||||||
if parts[3] == "" {
|
"16": "short",
|
||||||
return fmt.Sprintf("int%s", parts[2])
|
"32": "int",
|
||||||
|
"64": "long",
|
||||||
|
}[parts[2]]
|
||||||
|
|
||||||
|
//default to BigInt
|
||||||
|
if namedSize == "" {
|
||||||
|
namedSize = "BigInt"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("int%s[]", parts[2])
|
return len(parts[0]), namedSize
|
||||||
}
|
|
||||||
}
|
|
||||||
if parts[3] == "" {
|
|
||||||
return fmt.Sprintf("BigInt")
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("BigInts")
|
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "bool"):
|
case strings.HasPrefix(stringKind, "bool"):
|
||||||
parts := regexp.MustCompile(`bool(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
return len("bool"), "boolean"
|
||||||
if len(parts) != 2 {
|
|
||||||
return stringKind
|
|
||||||
}
|
|
||||||
if parts[1] == "" {
|
|
||||||
return fmt.Sprintf("bool")
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("bool[]")
|
|
||||||
|
|
||||||
case strings.HasPrefix(stringKind, "string"):
|
case strings.HasPrefix(stringKind, "string"):
|
||||||
parts := regexp.MustCompile(`string(\[[0-9]*\])?`).FindStringSubmatch(stringKind)
|
return len("string"), "String"
|
||||||
if len(parts) != 2 {
|
|
||||||
return stringKind
|
|
||||||
}
|
|
||||||
if parts[1] == "" {
|
|
||||||
return fmt.Sprintf("String")
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("String[]")
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return stringKind
|
return len(stringKind), stringKind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,11 +354,13 @@ func namedTypeJava(javaKind string, solKind abi.Type) string {
|
||||||
return "String"
|
return "String"
|
||||||
case "string[]":
|
case "string[]":
|
||||||
return "Strings"
|
return "Strings"
|
||||||
case "bool":
|
case "boolean":
|
||||||
return "Bool"
|
return "Bool"
|
||||||
case "bool[]":
|
case "boolean[]":
|
||||||
return "Bools"
|
return "Bools"
|
||||||
case "BigInt":
|
case "BigInt[]":
|
||||||
|
return "BigInts"
|
||||||
|
default:
|
||||||
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
|
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
|
||||||
if len(parts) != 4 {
|
if len(parts) != 4 {
|
||||||
return javaKind
|
return javaKind
|
||||||
|
|
@ -344,33 +375,29 @@ func namedTypeJava(javaKind string, solKind abi.Type) string {
|
||||||
default:
|
default:
|
||||||
return javaKind
|
return javaKind
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return javaKind
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// methodNormalizer is a name transformer that modifies Solidity method names to
|
// methodNormalizer is a name transformer that modifies Solidity method names to
|
||||||
// conform to target language naming concentions.
|
// conform to target language naming concentions.
|
||||||
var methodNormalizer = map[Lang]func(string) string{
|
var methodNormalizer = map[Lang]func(string) string{
|
||||||
LangGo: capitalise,
|
LangGo: abi.ToCamelCase,
|
||||||
LangJava: decapitalise,
|
LangJava: decapitalise,
|
||||||
}
|
}
|
||||||
|
|
||||||
// capitalise makes the first character of a string upper case, also removing any
|
// capitalise makes a camel-case string which starts with an upper case character.
|
||||||
// prefixing underscores from the variable names.
|
|
||||||
func capitalise(input string) string {
|
func capitalise(input string) string {
|
||||||
for len(input) > 0 && input[0] == '_' {
|
return abi.ToCamelCase(input)
|
||||||
input = input[1:]
|
|
||||||
}
|
|
||||||
if len(input) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return strings.ToUpper(input[:1]) + input[1:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// decapitalise makes the first character of a string lower case.
|
// decapitalise makes a camel-case string which starts with a lower case character.
|
||||||
func decapitalise(input string) string {
|
func decapitalise(input string) string {
|
||||||
return strings.ToLower(input[:1]) + input[1:]
|
if len(input) == 0 {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
goForm := abi.ToCamelCase(input)
|
||||||
|
return strings.ToLower(goForm[:1]) + goForm[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// structured checks whether a list of ABI data types has enough information to
|
// structured checks whether a list of ABI data types has enough information to
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,30 @@ const tmplSourceGo = `
|
||||||
|
|
||||||
package {{.Package}}
|
package {{.Package}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var (
|
||||||
|
_ = big.NewInt
|
||||||
|
_ = strings.NewReader
|
||||||
|
_ = ethereum.NotFound
|
||||||
|
_ = abi.U256
|
||||||
|
_ = bind.Bind
|
||||||
|
_ = common.Big1
|
||||||
|
_ = types.BloomLookup
|
||||||
|
_ = event.NewSubscription
|
||||||
|
)
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
{{range $contract := .Contracts}}
|
||||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||||
const {{.Type}}ABI = "{{.InputABI}}"
|
const {{.Type}}ABI = "{{.InputABI}}"
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,11 @@ var waitDeployedTests = map[string]struct {
|
||||||
|
|
||||||
func TestWaitDeployed(t *testing.T) {
|
func TestWaitDeployed(t *testing.T) {
|
||||||
for name, test := range waitDeployedTests {
|
for name, test := range waitDeployedTests {
|
||||||
backend := backends.NewSimulatedBackend(core.GenesisAlloc{
|
backend := backends.NewSimulatedBackend(
|
||||||
|
core.GenesisAlloc{
|
||||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
|
||||||
})
|
}, 10000000,
|
||||||
|
)
|
||||||
|
|
||||||
// Create the transaction.
|
// Create the transaction.
|
||||||
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code))
|
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code))
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,15 @@ type Event struct {
|
||||||
Inputs Arguments
|
Inputs Arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
func (event Event) String() string {
|
func (e Event) String() string {
|
||||||
inputs := make([]string, len(event.Inputs))
|
inputs := make([]string, len(e.Inputs))
|
||||||
for i, input := range event.Inputs {
|
for i, input := range e.Inputs {
|
||||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
|
||||||
if input.Indexed {
|
if input.Indexed {
|
||||||
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
|
inputs[i] = fmt.Sprintf("%v indexed %v", input.Type, input.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
|
return fmt.Sprintf("event %v(%v)", e.Name, strings.Join(inputs, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id returns the canonical representation of the event's signature used by the
|
// Id returns the canonical representation of the event's signature used by the
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,28 @@ var jsonEventPledge = []byte(`{
|
||||||
"type": "event"
|
"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
|
// 1000000
|
||||||
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||||
|
|
||||||
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||||
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
// 1000000,2218516807680,1000001
|
||||||
|
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||||
|
|
||||||
func TestEventId(t *testing.T) {
|
func TestEventId(t *testing.T) {
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
|
|
@ -71,12 +87,12 @@ func TestEventId(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
definition: `[
|
definition: `[
|
||||||
{ "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
{ "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
||||||
]`,
|
]`,
|
||||||
expectations: map[string]common.Hash{
|
expectations: map[string]common.Hash{
|
||||||
"balance": crypto.Keccak256Hash([]byte("balance(uint256)")),
|
"Balance": crypto.Keccak256Hash([]byte("Balance(uint256)")),
|
||||||
"check": crypto.Keccak256Hash([]byte("check(address,uint256)")),
|
"Check": crypto.Keccak256Hash([]byte("Check(address,uint256)")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -95,6 +111,39 @@ func TestEventId(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEventString(t *testing.T) {
|
||||||
|
var table = []struct {
|
||||||
|
definition string
|
||||||
|
expectations map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
definition: `[
|
||||||
|
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Transfer", "inputs": [{ "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256" }] }
|
||||||
|
]`,
|
||||||
|
expectations: map[string]string{
|
||||||
|
"Balance": "event Balance(uint256 in)",
|
||||||
|
"Check": "event Check(address t, uint256 b)",
|
||||||
|
"Transfer": "event Transfer(address indexed from, address indexed to, uint256 value)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.String() != test.expectations[name] {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expectations[name], event.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||||
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
|
|
@ -121,6 +170,27 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
Value *big.Int
|
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 {
|
type EventPledge struct {
|
||||||
Who common.Address
|
Who common.Address
|
||||||
Wad *big.Int
|
Wad *big.Int
|
||||||
|
|
@ -133,9 +203,16 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
Currency [3]byte
|
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)
|
bigint := new(big.Int)
|
||||||
bigintExpected := big.NewInt(1000000)
|
bigintExpected := big.NewInt(1000000)
|
||||||
bigintExpected2 := big.NewInt(2218516807680)
|
bigintExpected2 := big.NewInt(2218516807680)
|
||||||
|
bigintExpected3 := big.NewInt(1000001)
|
||||||
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
data string
|
data string
|
||||||
|
|
@ -158,6 +235,34 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
jsonEventTransfer,
|
jsonEventTransfer,
|
||||||
"",
|
"",
|
||||||
"Can unpack ERC20 Transfer event into slice",
|
"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,
|
pledgeData1,
|
||||||
&EventPledge{},
|
&EventPledge{},
|
||||||
|
|
@ -216,6 +321,13 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
jsonEventPledge,
|
jsonEventPledge,
|
||||||
"abi: cannot unmarshal tuple into map[string]interface {}",
|
"abi: cannot unmarshal tuple into map[string]interface {}",
|
||||||
"Can not unpack Pledge event into map",
|
"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 {
|
for _, tc := range testCases {
|
||||||
|
|
@ -227,7 +339,7 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
assert.Nil(err, "Should be able to unpack event data.")
|
assert.Nil(err, "Should be able to unpack event data.")
|
||||||
assert.Equal(tc.expected, tc.dest, tc.name)
|
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||||
} else {
|
} else {
|
||||||
assert.EqualError(err, tc.error)
|
assert.EqualError(err, tc.error, tc.name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,8 @@ type Method struct {
|
||||||
// Please note that "int" is substitute for its canonical representation "int256"
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
func (method Method) Sig() string {
|
func (method Method) Sig() string {
|
||||||
types := make([]string, len(method.Inputs))
|
types := make([]string, len(method.Inputs))
|
||||||
i := 0
|
for i, input := range method.Inputs {
|
||||||
for _, input := range method.Inputs {
|
|
||||||
types[i] = input.Type.String()
|
types[i] = input.Type.String()
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
||||||
}
|
}
|
||||||
|
|
@ -58,14 +56,14 @@ func (method Method) Sig() string {
|
||||||
func (method Method) String() string {
|
func (method Method) String() string {
|
||||||
inputs := make([]string, len(method.Inputs))
|
inputs := make([]string, len(method.Inputs))
|
||||||
for i, input := range method.Inputs {
|
for i, input := range method.Inputs {
|
||||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
|
||||||
}
|
}
|
||||||
outputs := make([]string, len(method.Outputs))
|
outputs := make([]string, len(method.Outputs))
|
||||||
for i, output := range method.Outputs {
|
for i, output := range method.Outputs {
|
||||||
|
outputs[i] = output.Type.String()
|
||||||
if len(output.Name) > 0 {
|
if len(output.Name) > 0 {
|
||||||
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
outputs[i] += fmt.Sprintf(" %v", output.Name)
|
||||||
}
|
}
|
||||||
outputs[i] += output.Type.String()
|
|
||||||
}
|
}
|
||||||
constant := ""
|
constant := ""
|
||||||
if method.Const {
|
if method.Const {
|
||||||
|
|
|
||||||
61
accounts/abi/method_test.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// 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 (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const methoddata = `
|
||||||
|
[
|
||||||
|
{ "type" : "function", "name" : "balance", "constant" : true },
|
||||||
|
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
|
||||||
|
{ "type" : "function", "name" : "transfer", "constant" : false, "inputs" : [ { "name" : "from", "type" : "address" }, { "name" : "to", "type" : "address" }, { "name" : "value", "type" : "uint256" } ], "outputs" : [ { "name" : "success", "type" : "bool" } ] }
|
||||||
|
]`
|
||||||
|
|
||||||
|
func TestMethodString(t *testing.T) {
|
||||||
|
var table = []struct {
|
||||||
|
method string
|
||||||
|
expectation string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
method: "balance",
|
||||||
|
expectation: "function balance() constant returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "send",
|
||||||
|
expectation: "function send(uint256 amount) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "transfer",
|
||||||
|
expectation: "function transfer(address from, address to, uint256 value) returns(bool success)",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
got := abi.Methods[test.method].String()
|
||||||
|
if got != test.expectation {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expectation, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,35 +25,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
big_t = reflect.TypeOf(&big.Int{})
|
bigT = reflect.TypeOf(&big.Int{})
|
||||||
derefbig_t = reflect.TypeOf(big.Int{})
|
derefbigT = reflect.TypeOf(big.Int{})
|
||||||
uint8_t = reflect.TypeOf(uint8(0))
|
uint8T = reflect.TypeOf(uint8(0))
|
||||||
uint16_t = reflect.TypeOf(uint16(0))
|
uint16T = reflect.TypeOf(uint16(0))
|
||||||
uint32_t = reflect.TypeOf(uint32(0))
|
uint32T = reflect.TypeOf(uint32(0))
|
||||||
uint64_t = reflect.TypeOf(uint64(0))
|
uint64T = reflect.TypeOf(uint64(0))
|
||||||
int_t = reflect.TypeOf(int(0))
|
int8T = reflect.TypeOf(int8(0))
|
||||||
int8_t = reflect.TypeOf(int8(0))
|
int16T = reflect.TypeOf(int16(0))
|
||||||
int16_t = reflect.TypeOf(int16(0))
|
int32T = reflect.TypeOf(int32(0))
|
||||||
int32_t = reflect.TypeOf(int32(0))
|
int64T = reflect.TypeOf(int64(0))
|
||||||
int64_t = reflect.TypeOf(int64(0))
|
addressT = reflect.TypeOf(common.Address{})
|
||||||
address_t = reflect.TypeOf(common.Address{})
|
|
||||||
int_ts = reflect.TypeOf([]int(nil))
|
|
||||||
int8_ts = reflect.TypeOf([]int8(nil))
|
|
||||||
int16_ts = reflect.TypeOf([]int16(nil))
|
|
||||||
int32_ts = reflect.TypeOf([]int32(nil))
|
|
||||||
int64_ts = reflect.TypeOf([]int64(nil))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// U256 converts a big Int into a 256bit EVM number.
|
// U256 converts a big Int into a 256bit EVM number.
|
||||||
func U256(n *big.Int) []byte {
|
func U256(n *big.Int) []byte {
|
||||||
return math.PaddedBigBytes(math.U256(n), 32)
|
return math.PaddedBigBytes(math.U256(n), 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks whether the given reflect value is signed. This also works for slices with a number type
|
|
||||||
func isSigned(v reflect.Value) bool {
|
|
||||||
switch v.Type() {
|
|
||||||
case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package abi
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -32,13 +31,3 @@ func TestNumberTypes(t *testing.T) {
|
||||||
t.Errorf("expected %x got %x", ubytes, unsigned)
|
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSigned(t *testing.T) {
|
|
||||||
if isSigned(reflect.ValueOf(uint(10))) {
|
|
||||||
t.Error("signed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isSigned(reflect.ValueOf(int(10))) {
|
|
||||||
t.Error("not signed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -30,308 +30,600 @@ import (
|
||||||
func TestPack(t *testing.T) {
|
func TestPack(t *testing.T) {
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
typ string
|
typ string
|
||||||
|
components []ArgumentMarshaling
|
||||||
input interface{}
|
input interface{}
|
||||||
output []byte
|
output []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"uint8",
|
"uint8",
|
||||||
|
nil,
|
||||||
uint8(2),
|
uint8(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint8[]",
|
"uint8[]",
|
||||||
|
nil,
|
||||||
[]uint8{1, 2},
|
[]uint8{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint16",
|
"uint16",
|
||||||
|
nil,
|
||||||
uint16(2),
|
uint16(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint16[]",
|
"uint16[]",
|
||||||
|
nil,
|
||||||
[]uint16{1, 2},
|
[]uint16{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint32",
|
"uint32",
|
||||||
|
nil,
|
||||||
uint32(2),
|
uint32(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint32[]",
|
"uint32[]",
|
||||||
|
nil,
|
||||||
[]uint32{1, 2},
|
[]uint32{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint64",
|
"uint64",
|
||||||
|
nil,
|
||||||
uint64(2),
|
uint64(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint64[]",
|
"uint64[]",
|
||||||
|
nil,
|
||||||
[]uint64{1, 2},
|
[]uint64{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint256",
|
"uint256",
|
||||||
|
nil,
|
||||||
big.NewInt(2),
|
big.NewInt(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uint256[]",
|
"uint256[]",
|
||||||
|
nil,
|
||||||
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int8",
|
"int8",
|
||||||
|
nil,
|
||||||
int8(2),
|
int8(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int8[]",
|
"int8[]",
|
||||||
|
nil,
|
||||||
[]int8{1, 2},
|
[]int8{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int16",
|
"int16",
|
||||||
|
nil,
|
||||||
int16(2),
|
int16(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int16[]",
|
"int16[]",
|
||||||
|
nil,
|
||||||
[]int16{1, 2},
|
[]int16{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int32",
|
"int32",
|
||||||
|
nil,
|
||||||
int32(2),
|
int32(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int32[]",
|
"int32[]",
|
||||||
|
nil,
|
||||||
[]int32{1, 2},
|
[]int32{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int64",
|
"int64",
|
||||||
|
nil,
|
||||||
int64(2),
|
int64(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int64[]",
|
"int64[]",
|
||||||
|
nil,
|
||||||
[]int64{1, 2},
|
[]int64{1, 2},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int256",
|
"int256",
|
||||||
|
nil,
|
||||||
big.NewInt(2),
|
big.NewInt(2),
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"int256[]",
|
"int256[]",
|
||||||
|
nil,
|
||||||
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes1",
|
"bytes1",
|
||||||
|
nil,
|
||||||
[1]byte{1},
|
[1]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes2",
|
"bytes2",
|
||||||
|
nil,
|
||||||
[2]byte{1},
|
[2]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes3",
|
"bytes3",
|
||||||
|
nil,
|
||||||
[3]byte{1},
|
[3]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes4",
|
"bytes4",
|
||||||
|
nil,
|
||||||
[4]byte{1},
|
[4]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes5",
|
"bytes5",
|
||||||
|
nil,
|
||||||
[5]byte{1},
|
[5]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes6",
|
"bytes6",
|
||||||
|
nil,
|
||||||
[6]byte{1},
|
[6]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes7",
|
"bytes7",
|
||||||
|
nil,
|
||||||
[7]byte{1},
|
[7]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes8",
|
"bytes8",
|
||||||
|
nil,
|
||||||
[8]byte{1},
|
[8]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes9",
|
"bytes9",
|
||||||
|
nil,
|
||||||
[9]byte{1},
|
[9]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes10",
|
"bytes10",
|
||||||
|
nil,
|
||||||
[10]byte{1},
|
[10]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes11",
|
"bytes11",
|
||||||
|
nil,
|
||||||
[11]byte{1},
|
[11]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes12",
|
"bytes12",
|
||||||
|
nil,
|
||||||
[12]byte{1},
|
[12]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes13",
|
"bytes13",
|
||||||
|
nil,
|
||||||
[13]byte{1},
|
[13]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes14",
|
"bytes14",
|
||||||
|
nil,
|
||||||
[14]byte{1},
|
[14]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes15",
|
"bytes15",
|
||||||
|
nil,
|
||||||
[15]byte{1},
|
[15]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes16",
|
"bytes16",
|
||||||
|
nil,
|
||||||
[16]byte{1},
|
[16]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes17",
|
"bytes17",
|
||||||
|
nil,
|
||||||
[17]byte{1},
|
[17]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes18",
|
"bytes18",
|
||||||
|
nil,
|
||||||
[18]byte{1},
|
[18]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes19",
|
"bytes19",
|
||||||
|
nil,
|
||||||
[19]byte{1},
|
[19]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes20",
|
"bytes20",
|
||||||
|
nil,
|
||||||
[20]byte{1},
|
[20]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes21",
|
"bytes21",
|
||||||
|
nil,
|
||||||
[21]byte{1},
|
[21]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes22",
|
"bytes22",
|
||||||
|
nil,
|
||||||
[22]byte{1},
|
[22]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes23",
|
"bytes23",
|
||||||
|
nil,
|
||||||
[23]byte{1},
|
[23]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes24",
|
"bytes24",
|
||||||
[24]byte{1},
|
nil,
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bytes24",
|
|
||||||
[24]byte{1},
|
[24]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes25",
|
"bytes25",
|
||||||
|
nil,
|
||||||
[25]byte{1},
|
[25]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes26",
|
"bytes26",
|
||||||
|
nil,
|
||||||
[26]byte{1},
|
[26]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes27",
|
"bytes27",
|
||||||
|
nil,
|
||||||
[27]byte{1},
|
[27]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes28",
|
"bytes28",
|
||||||
|
nil,
|
||||||
[28]byte{1},
|
[28]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes29",
|
"bytes29",
|
||||||
|
nil,
|
||||||
[29]byte{1},
|
[29]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes30",
|
"bytes30",
|
||||||
|
nil,
|
||||||
[30]byte{1},
|
[30]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes31",
|
"bytes31",
|
||||||
|
nil,
|
||||||
[31]byte{1},
|
[31]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes32",
|
"bytes32",
|
||||||
|
nil,
|
||||||
[32]byte{1},
|
[32]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"uint32[2][3][4]",
|
||||||
|
nil,
|
||||||
|
[4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"address[]",
|
"address[]",
|
||||||
|
nil,
|
||||||
[]common.Address{{1}, {2}},
|
[]common.Address{{1}, {2}},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes32[]",
|
"bytes32[]",
|
||||||
|
nil,
|
||||||
[]common.Hash{{1}, {2}},
|
[]common.Hash{{1}, {2}},
|
||||||
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"function",
|
"function",
|
||||||
|
nil,
|
||||||
[24]byte{1},
|
[24]byte{1},
|
||||||
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"string",
|
"string",
|
||||||
|
nil,
|
||||||
"foobar",
|
"foobar",
|
||||||
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"string[]",
|
||||||
|
nil,
|
||||||
|
[]string{"hello", "foobar"},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
|
||||||
|
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string[2]",
|
||||||
|
nil,
|
||||||
|
[]string{"hello", "foobar"},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
|
||||||
|
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes32[][]",
|
||||||
|
nil,
|
||||||
|
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"bytes32[][2]",
|
||||||
|
nil,
|
||||||
|
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"bytes32[3][2]",
|
||||||
|
nil,
|
||||||
|
[][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// static tuple
|
||||||
|
"tuple",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int64"},
|
||||||
|
{Name: "b", Type: "int256"},
|
||||||
|
{Name: "c", Type: "int256"},
|
||||||
|
{Name: "d", Type: "bool"},
|
||||||
|
{Name: "e", Type: "bytes32[3][2]"},
|
||||||
|
},
|
||||||
|
struct {
|
||||||
|
A int64
|
||||||
|
B *big.Int
|
||||||
|
C *big.Int
|
||||||
|
D bool
|
||||||
|
E [][]common.Hash
|
||||||
|
}{1, big.NewInt(1), big.NewInt(-1), true, [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001" + // struct[a]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[d]
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // struct[e] array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// dynamic tuple
|
||||||
|
"tuple",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "string"},
|
||||||
|
{Name: "b", Type: "int64"},
|
||||||
|
{Name: "c", Type: "bytes"},
|
||||||
|
{Name: "d", Type: "string[]"},
|
||||||
|
{Name: "e", Type: "int256[]"},
|
||||||
|
{Name: "f", Type: "address[]"},
|
||||||
|
},
|
||||||
|
struct {
|
||||||
|
FieldA string `abi:"a"` // Test whether abi tag works
|
||||||
|
FieldB int64 `abi:"b"`
|
||||||
|
C []byte
|
||||||
|
D []string
|
||||||
|
E []*big.Int
|
||||||
|
F []common.Address
|
||||||
|
}{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}},
|
||||||
|
common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1}
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // foo offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // bar offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // foo length
|
||||||
|
"666f6f0000000000000000000000000000000000000000000000000000000000" + // foo
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // bar offset
|
||||||
|
"6261720000000000000000000000000000000000000000000000000000000000" + // bar
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // 1
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length
|
||||||
|
"0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1}
|
||||||
|
"0000000000000000000000000200000000000000000000000000000000000000"), // common.Address{2}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// nested tuple
|
||||||
|
"tuple",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "tuple", Components: []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256[]"}}},
|
||||||
|
{Name: "b", Type: "int256[]"},
|
||||||
|
},
|
||||||
|
struct {
|
||||||
|
A struct {
|
||||||
|
FieldA *big.Int `abi:"a"`
|
||||||
|
B []*big.Int
|
||||||
|
}
|
||||||
|
B []*big.Int
|
||||||
|
}{
|
||||||
|
A: struct {
|
||||||
|
FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple
|
||||||
|
B []*big.Int
|
||||||
|
}{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
|
||||||
|
B: []*big.Int{big.NewInt(1), big.NewInt(0)}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // a offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" + // b offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // a.a value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" + // a.b[1] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // b length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000"), // b[1] value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// tuple slice
|
||||||
|
"tuple[]",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256"},
|
||||||
|
{Name: "b", Type: "int256[]"},
|
||||||
|
},
|
||||||
|
[]struct {
|
||||||
|
A *big.Int
|
||||||
|
B []*big.Int
|
||||||
|
}{
|
||||||
|
{big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
|
||||||
|
{big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}},
|
||||||
|
},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // tuple length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" + // tuple[0].B[1] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].B[1] value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// static tuple array
|
||||||
|
"tuple[2]",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256"},
|
||||||
|
{Name: "b", Type: "int256"},
|
||||||
|
},
|
||||||
|
[2]struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{
|
||||||
|
{big.NewInt(-1), big.NewInt(1)},
|
||||||
|
{big.NewInt(1), big.NewInt(-1)},
|
||||||
|
},
|
||||||
|
common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].b
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// dynamic tuple array
|
||||||
|
"tuple[2]",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256[]"},
|
||||||
|
},
|
||||||
|
[2]struct {
|
||||||
|
A []*big.Int
|
||||||
|
}{
|
||||||
|
{[]*big.Int{big.NewInt(-1), big.NewInt(1)}},
|
||||||
|
{[]*big.Int{big.NewInt(1), big.NewInt(-1)}},
|
||||||
|
},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0]
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
typ, err := NewType(test.typ)
|
typ, err := NewType(test.typ, test.components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
|
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := typ.pack(reflect.ValueOf(test.input))
|
output, err := typ.pack(reflect.ValueOf(test.input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
|
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(output, test.output) {
|
if !bytes.Equal(output, test.output) {
|
||||||
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
|
t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -401,6 +693,59 @@ func TestMethodPack(t *testing.T) {
|
||||||
if !bytes.Equal(packed, sig) {
|
if !bytes.Equal(packed, sig) {
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}}
|
||||||
|
sig = abi.Methods["nestedArray"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 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("nestedArray", a, []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["nestedArray2"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["nestedSlice"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPackNumber(t *testing.T) {
|
func TestPackNumber(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@ package abi
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// indirect recursively dereferences the value until it either gets the value
|
// indirect recursively dereferences the value until it either gets the value
|
||||||
// or finds a big.Int
|
// or finds a big.Int
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
func indirect(v reflect.Value) reflect.Value {
|
||||||
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t {
|
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT {
|
||||||
return indirect(v.Elem())
|
return indirect(v.Elem())
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
|
@ -36,26 +37,26 @@ func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type)
|
||||||
switch size {
|
switch size {
|
||||||
case 8:
|
case 8:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint8, uint8_t
|
return reflect.Uint8, uint8T
|
||||||
}
|
}
|
||||||
return reflect.Int8, int8_t
|
return reflect.Int8, int8T
|
||||||
case 16:
|
case 16:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint16, uint16_t
|
return reflect.Uint16, uint16T
|
||||||
}
|
}
|
||||||
return reflect.Int16, int16_t
|
return reflect.Int16, int16T
|
||||||
case 32:
|
case 32:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint32, uint32_t
|
return reflect.Uint32, uint32T
|
||||||
}
|
}
|
||||||
return reflect.Int32, int32_t
|
return reflect.Int32, int32T
|
||||||
case 64:
|
case 64:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint64, uint64_t
|
return reflect.Uint64, uint64T
|
||||||
}
|
}
|
||||||
return reflect.Int64, int64_t
|
return reflect.Int64, int64T
|
||||||
}
|
}
|
||||||
return reflect.Ptr, big_t
|
return reflect.Ptr, bigT
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
|
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
|
||||||
|
|
@ -70,22 +71,36 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
||||||
//
|
//
|
||||||
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
||||||
// strict ruleset as bare `reflect` does.
|
// strict ruleset as bare `reflect` does.
|
||||||
func set(dst, src reflect.Value, output Argument) error {
|
func set(dst, src reflect.Value) error {
|
||||||
dstType := dst.Type()
|
dstType, srcType := dst.Type(), src.Type()
|
||||||
srcType := src.Type()
|
|
||||||
switch {
|
switch {
|
||||||
case dstType.AssignableTo(srcType):
|
case dstType.Kind() == reflect.Interface && dst.Elem().IsValid():
|
||||||
|
return set(dst.Elem(), src)
|
||||||
|
case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT:
|
||||||
|
return set(dst.Elem(), src)
|
||||||
|
case srcType.AssignableTo(dstType) && dst.CanSet():
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
case dstType.Kind() == reflect.Interface:
|
case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice:
|
||||||
dst.Set(src)
|
return setSlice(dst, src)
|
||||||
case dstType.Kind() == reflect.Ptr:
|
|
||||||
return set(dst.Elem(), src, output)
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setSlice attempts to assign src to dst when slices are not assignable by default
|
||||||
|
// e.g. src: [][]byte -> dst: [][15]byte
|
||||||
|
func setSlice(dst, src reflect.Value) error {
|
||||||
|
slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len())
|
||||||
|
for i := 0; i < src.Len(); i++ {
|
||||||
|
v := src.Index(i)
|
||||||
|
reflect.Copy(slice.Index(i), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// requireAssignable assures that `dest` is a pointer and it's not an interface.
|
// requireAssignable assures that `dest` is a pointer and it's not an interface.
|
||||||
func requireAssignable(dst, src reflect.Value) error {
|
func requireAssignable(dst, src reflect.Value) error {
|
||||||
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
|
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
|
||||||
|
|
@ -110,3 +125,94 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapArgNamesToStructFields maps a slice of argument names to struct fields.
|
||||||
|
// first round: for each Exportable field that contains a `abi:""` tag
|
||||||
|
// and this field name exists in the given argument name list, pair them together.
|
||||||
|
// second round: for each argument name 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.
|
||||||
|
// Note this function assumes the given value is a struct value.
|
||||||
|
func mapArgNamesToStructFields(argNames []string, 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 _, arg := range argNames {
|
||||||
|
if arg == tagName {
|
||||||
|
if abi2struct[arg] != "" {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
|
||||||
|
}
|
||||||
|
// pair them
|
||||||
|
abi2struct[arg] = structFieldName
|
||||||
|
struct2abi[structFieldName] = arg
|
||||||
|
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 _, argName := range argNames {
|
||||||
|
|
||||||
|
structFieldName := ToCamelCase(argName)
|
||||||
|
|
||||||
|
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[argName] != "" {
|
||||||
|
if abi2struct[argName] != structFieldName &&
|
||||||
|
struct2abi[structFieldName] == "" &&
|
||||||
|
value.FieldByName(structFieldName).IsValid() {
|
||||||
|
return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName)
|
||||||
|
}
|
||||||
|
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[argName] = structFieldName
|
||||||
|
struct2abi[structFieldName] = argName
|
||||||
|
} else {
|
||||||
|
// not paired, but annotate as used, to detect cases like
|
||||||
|
// abi : [ { "name": "value" }, { "name": "_value" } ]
|
||||||
|
// struct { Value *big.Int }
|
||||||
|
struct2abi[structFieldName] = argName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return abi2struct, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
191
accounts/abi/reflect_test.go
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reflectTest struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
struc interface{}
|
||||||
|
want map[string]string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
var reflectTests = []reflectTest{
|
||||||
|
{
|
||||||
|
name: "OneToOneCorrespondance",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MissingFieldsInStruct",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MoreFieldsInStructThanArgs",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MissingFieldInArgs",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag 'fieldB' defined but not found in abi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoAbiDescriptor",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoArgs",
|
||||||
|
args: []string{},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag 'fieldA' defined but not found in abi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DifferentName",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldB": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DifferentName",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldB": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFields",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
"fieldB": "FieldB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFieldsABIMissing",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
"fieldB": "FieldB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NameConflict",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple variables maps to the same abi field 'fieldB'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Underscored",
|
||||||
|
args: []string{"_"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int
|
||||||
|
}{},
|
||||||
|
err: "abi: purely underscored output cannot unpack to struct",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DoubleMapping",
|
||||||
|
args: []string{"fieldB", "fieldC", "fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldC"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple outputs mapping to the same struct field 'FieldA'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AlreadyMapped",
|
||||||
|
args: []string{"fieldB", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag in 'FieldB' already mapped",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReflectNameToStruct(t *testing.T) {
|
||||||
|
for _, test := range reflectTests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
||||||
|
if len(test.err) > 0 {
|
||||||
|
if err == nil || err.Error() != test.err {
|
||||||
|
t.Fatalf("Invalid error: expected %v, got %v", test.err, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
for fname := range test.want {
|
||||||
|
if m[fname] != test.want[fname] {
|
||||||
|
t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -32,6 +33,7 @@ const (
|
||||||
StringTy
|
StringTy
|
||||||
SliceTy
|
SliceTy
|
||||||
ArrayTy
|
ArrayTy
|
||||||
|
TupleTy
|
||||||
AddressTy
|
AddressTy
|
||||||
FixedBytesTy
|
FixedBytesTy
|
||||||
BytesTy
|
BytesTy
|
||||||
|
|
@ -43,13 +45,16 @@ const (
|
||||||
// Type is the reflection of the supported argument type
|
// Type is the reflection of the supported argument type
|
||||||
type Type struct {
|
type Type struct {
|
||||||
Elem *Type
|
Elem *Type
|
||||||
|
|
||||||
Kind reflect.Kind
|
Kind reflect.Kind
|
||||||
Type reflect.Type
|
Type reflect.Type
|
||||||
Size int
|
Size int
|
||||||
T byte // Our own type checking
|
T byte // Our own type checking
|
||||||
|
|
||||||
stringKind string // holds the unparsed string for deriving signatures
|
stringKind string // holds the unparsed string for deriving signatures
|
||||||
|
|
||||||
|
// Tuple relative fields
|
||||||
|
TupleElems []*Type // Type information of all tuple fields
|
||||||
|
TupleRawNames []string // Raw field name of all tuple fields
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -58,7 +63,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewType creates a new reflection type of abi type given in t.
|
// NewType creates a new reflection type of abi type given in t.
|
||||||
func NewType(t string) (typ Type, err error) {
|
func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
// check that array brackets are equal if they exist
|
// check that array brackets are equal if they exist
|
||||||
if strings.Count(t, "[") != strings.Count(t, "]") {
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
return Type{}, fmt.Errorf("invalid arg type in abi")
|
return Type{}, fmt.Errorf("invalid arg type in abi")
|
||||||
|
|
@ -71,7 +76,7 @@ func NewType(t string) (typ Type, err error) {
|
||||||
if strings.Count(t, "[") != 0 {
|
if strings.Count(t, "[") != 0 {
|
||||||
i := strings.LastIndex(t, "[")
|
i := strings.LastIndex(t, "[")
|
||||||
// recursively embed the type
|
// recursively embed the type
|
||||||
embeddedType, err := NewType(t[:i])
|
embeddedType, err := NewType(t[:i], components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Type{}, err
|
return Type{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -87,6 +92,9 @@ func NewType(t string) (typ Type, err error) {
|
||||||
typ.Kind = reflect.Slice
|
typ.Kind = reflect.Slice
|
||||||
typ.Elem = &embeddedType
|
typ.Elem = &embeddedType
|
||||||
typ.Type = reflect.SliceOf(embeddedType.Type)
|
typ.Type = reflect.SliceOf(embeddedType.Type)
|
||||||
|
if embeddedType.T == TupleTy {
|
||||||
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
|
}
|
||||||
} else if len(intz) == 1 {
|
} else if len(intz) == 1 {
|
||||||
// is a array
|
// is a array
|
||||||
typ.T = ArrayTy
|
typ.T = ArrayTy
|
||||||
|
|
@ -97,13 +105,21 @@ func NewType(t string) (typ Type, err error) {
|
||||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
}
|
}
|
||||||
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
||||||
|
if embeddedType.T == TupleTy {
|
||||||
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
return typ, err
|
return typ, err
|
||||||
}
|
}
|
||||||
// parse the type and size of the abi-type.
|
// parse the type and size of the abi-type.
|
||||||
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
matches := typeRegex.FindAllStringSubmatch(t, -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return Type{}, fmt.Errorf("invalid type '%v'", t)
|
||||||
|
}
|
||||||
|
parsedType := matches[0]
|
||||||
|
|
||||||
// varSize is the size of the variable
|
// varSize is the size of the variable
|
||||||
var varSize int
|
var varSize int
|
||||||
if len(parsedType[3]) > 0 {
|
if len(parsedType[3]) > 0 {
|
||||||
|
|
@ -135,7 +151,7 @@ func NewType(t string) (typ Type, err error) {
|
||||||
typ.Type = reflect.TypeOf(bool(false))
|
typ.Type = reflect.TypeOf(bool(false))
|
||||||
case "address":
|
case "address":
|
||||||
typ.Kind = reflect.Array
|
typ.Kind = reflect.Array
|
||||||
typ.Type = address_t
|
typ.Type = addressT
|
||||||
typ.Size = 20
|
typ.Size = 20
|
||||||
typ.T = AddressTy
|
typ.T = AddressTy
|
||||||
case "string":
|
case "string":
|
||||||
|
|
@ -153,6 +169,40 @@ func NewType(t string) (typ Type, err error) {
|
||||||
typ.Size = varSize
|
typ.Size = varSize
|
||||||
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||||
}
|
}
|
||||||
|
case "tuple":
|
||||||
|
var (
|
||||||
|
fields []reflect.StructField
|
||||||
|
elems []*Type
|
||||||
|
names []string
|
||||||
|
expression string // canonical parameter expression
|
||||||
|
)
|
||||||
|
expression += "("
|
||||||
|
for idx, c := range components {
|
||||||
|
cType, err := NewType(c.Type, c.Components)
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, err
|
||||||
|
}
|
||||||
|
if ToCamelCase(c.Name) == "" {
|
||||||
|
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
|
||||||
|
}
|
||||||
|
fields = append(fields, reflect.StructField{
|
||||||
|
Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field.
|
||||||
|
Type: cType.Type,
|
||||||
|
})
|
||||||
|
elems = append(elems, &cType)
|
||||||
|
names = append(names, c.Name)
|
||||||
|
expression += cType.stringKind
|
||||||
|
if idx != len(components)-1 {
|
||||||
|
expression += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expression += ")"
|
||||||
|
typ.Kind = reflect.Struct
|
||||||
|
typ.Type = reflect.StructOf(fields)
|
||||||
|
typ.TupleElems = elems
|
||||||
|
typ.TupleRawNames = names
|
||||||
|
typ.T = TupleTy
|
||||||
|
typ.stringKind = expression
|
||||||
case "function":
|
case "function":
|
||||||
typ.Kind = reflect.Array
|
typ.Kind = reflect.Array
|
||||||
typ.T = FunctionTy
|
typ.T = FunctionTy
|
||||||
|
|
@ -173,28 +223,82 @@ func (t Type) String() (out string) {
|
||||||
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||||
// dereference pointer first if it's a pointer
|
// dereference pointer first if it's a pointer
|
||||||
v = indirect(v)
|
v = indirect(v)
|
||||||
|
|
||||||
if err := typeCheck(t, v); err != nil {
|
if err := typeCheck(t, v); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.T == SliceTy || t.T == ArrayTy {
|
switch t.T {
|
||||||
var packed []byte
|
case SliceTy, ArrayTy:
|
||||||
|
var ret []byte
|
||||||
|
|
||||||
|
if t.requiresLengthPrefix() {
|
||||||
|
// append length
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(v.Len()))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate offset if any
|
||||||
|
offset := 0
|
||||||
|
offsetReq := isDynamicType(*t.Elem)
|
||||||
|
if offsetReq {
|
||||||
|
offset = getTypeSize(*t.Elem) * v.Len()
|
||||||
|
}
|
||||||
|
var tail []byte
|
||||||
for i := 0; i < v.Len(); i++ {
|
for i := 0; i < v.Len(); i++ {
|
||||||
val, err := t.Elem.pack(v.Index(i))
|
val, err := t.Elem.pack(v.Index(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
packed = append(packed, val...)
|
if !offsetReq {
|
||||||
|
ret = append(ret, val...)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if t.T == SliceTy {
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
return packBytesSlice(packed, v.Len()), nil
|
offset += len(val)
|
||||||
} else if t.T == ArrayTy {
|
tail = append(tail, val...)
|
||||||
return packed, nil
|
}
|
||||||
|
return append(ret, tail...), nil
|
||||||
|
case TupleTy:
|
||||||
|
// (T1,...,Tk) for k >= 0 and any types T1, …, Tk
|
||||||
|
// enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
|
||||||
|
// where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static
|
||||||
|
// type as
|
||||||
|
// head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string)
|
||||||
|
// and as
|
||||||
|
// head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1))))
|
||||||
|
// tail(X(i)) = enc(X(i))
|
||||||
|
// otherwise, i.e. if Ti is a dynamic type.
|
||||||
|
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Calculate prefix occupied size.
|
||||||
|
offset := 0
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
offset += getTypeSize(*elem)
|
||||||
|
}
|
||||||
|
var ret, tail []byte
|
||||||
|
for i, elem := range t.TupleElems {
|
||||||
|
field := v.FieldByName(fieldmap[t.TupleRawNames[i]])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i])
|
||||||
|
}
|
||||||
|
val, err := elem.pack(field)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isDynamicType(*elem) {
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
tail = append(tail, val...)
|
||||||
|
offset += len(val)
|
||||||
|
} else {
|
||||||
|
ret = append(ret, val...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return append(ret, tail...), nil
|
||||||
|
|
||||||
|
default:
|
||||||
return packElement(t, v), nil
|
return packElement(t, v), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireLengthPrefix returns whether the type requires any sort of length
|
// requireLengthPrefix returns whether the type requires any sort of length
|
||||||
|
|
@ -202,3 +306,47 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||||
func (t Type) requiresLengthPrefix() bool {
|
func (t Type) requiresLengthPrefix() bool {
|
||||||
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isDynamicType returns true if the type is dynamic.
|
||||||
|
// The following types are called “dynamic”:
|
||||||
|
// * bytes
|
||||||
|
// * string
|
||||||
|
// * T[] for any T
|
||||||
|
// * T[k] for any dynamic T and any k >= 0
|
||||||
|
// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k
|
||||||
|
func isDynamicType(t Type) bool {
|
||||||
|
if t.T == TupleTy {
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
if isDynamicType(*elem) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTypeSize returns the size that this type needs to occupy.
|
||||||
|
// We distinguish static and dynamic types. Static types are encoded in-place
|
||||||
|
// and dynamic types are encoded at a separately allocated location after the
|
||||||
|
// current block.
|
||||||
|
// So for a static variable, the size returned represents the size that the
|
||||||
|
// variable actually occupies.
|
||||||
|
// For a dynamic variable, the returned size is fixed 32 bytes, which is used
|
||||||
|
// to store the location reference for actual value storage.
|
||||||
|
func getTypeSize(t Type) int {
|
||||||
|
if t.T == ArrayTy && !isDynamicType(*t.Elem) {
|
||||||
|
// Recursively calculate type size if it is a nested array
|
||||||
|
if t.Elem.T == ArrayTy {
|
||||||
|
return t.Size * getTypeSize(*t.Elem)
|
||||||
|
}
|
||||||
|
return t.Size * 32
|
||||||
|
} else if t.T == TupleTy && !isDynamicType(t) {
|
||||||
|
total := 0
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
total += getTypeSize(*elem)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
return 32
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,71 +33,74 @@ type typeWithoutStringer Type
|
||||||
func TestTypeRegexp(t *testing.T) {
|
func TestTypeRegexp(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
blob string
|
blob string
|
||||||
|
components []ArgumentMarshaling
|
||||||
kind Type
|
kind Type
|
||||||
}{
|
}{
|
||||||
{"bool", Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
|
{"bool", nil, 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[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}},
|
||||||
{"bool[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}},
|
{"bool[2]", nil, Type{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[2][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
|
||||||
{"bool[][]", 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[][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
|
||||||
{"bool[][2]", 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]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
|
||||||
{"bool[2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
|
{"bool[2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.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]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
|
||||||
{"bool[2][2][2]", 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[2][2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
|
||||||
{"bool[][][]", 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[][][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
|
||||||
{"bool[][2][]", 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][]"}},
|
{"bool[][2][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
|
||||||
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
{"int8", nil, Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}},
|
||||||
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
|
{"int16", nil, Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}},
|
||||||
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
|
{"int32", nil, Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}},
|
||||||
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
|
{"int64", nil, Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}},
|
||||||
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
{"int256", nil, 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: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
{"int8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
||||||
{"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
{"int8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
||||||
{"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
{"int16[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
||||||
{"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
{"int16[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
||||||
{"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
{"int32[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
||||||
{"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
{"int32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
||||||
{"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
{"int64[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
||||||
{"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
{"int64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
||||||
{"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
{"int256[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
||||||
{"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
{"int256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
||||||
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
{"uint8", nil, Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||||
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
|
{"uint16", nil, Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||||
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
|
{"uint32", nil, Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||||
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
|
{"uint64", nil, Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||||
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
{"uint256", nil, 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: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
{"uint8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
||||||
{"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
{"uint8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
||||||
{"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
{"uint16[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
||||||
{"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
{"uint16[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
||||||
{"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
{"uint32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
||||||
{"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
{"uint32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
||||||
{"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
{"uint64[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
||||||
{"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
{"uint64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
||||||
{"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
{"uint256[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
||||||
{"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
{"uint256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
||||||
{"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
{"bytes32", nil, 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[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
||||||
{"bytes[2]", 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]"}},
|
{"bytes[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
||||||
{"bytes32[]", 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[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
||||||
{"bytes32[2]", 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]"}},
|
{"bytes32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
||||||
{"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
{"string", nil, 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[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
|
||||||
{"string[2]", 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]"}},
|
{"string[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
|
||||||
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
|
{"address", nil, 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: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
{"address[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
||||||
{"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
{"address[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
||||||
// TODO when fixed types are implemented properly
|
// TODO when fixed types are implemented properly
|
||||||
// {"fixed", Type{}},
|
// {"fixed", nil, Type{}},
|
||||||
// {"fixed128x128", Type{}},
|
// {"fixed128x128", nil, Type{}},
|
||||||
// {"fixed[]", Type{}},
|
// {"fixed[]", nil, Type{}},
|
||||||
// {"fixed[2]", Type{}},
|
// {"fixed[2]", nil, Type{}},
|
||||||
// {"fixed128x128[]", Type{}},
|
// {"fixed128x128[]", nil, Type{}},
|
||||||
// {"fixed128x128[2]", Type{}},
|
// {"fixed128x128[2]", nil, Type{}},
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct{ A int64 }{}), stringKind: "(int64)",
|
||||||
|
TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
typ, err := NewType(tt.blob)
|
typ, err := NewType(tt.blob, tt.components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
||||||
}
|
}
|
||||||
|
|
@ -110,150 +113,169 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
func TestTypeCheck(t *testing.T) {
|
func TestTypeCheck(t *testing.T) {
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
typ string
|
typ string
|
||||||
|
components []ArgumentMarshaling
|
||||||
input interface{}
|
input interface{}
|
||||||
err string
|
err string
|
||||||
}{
|
}{
|
||||||
{"uint", big.NewInt(1), "unsupported arg type: uint"},
|
{"uint", nil, big.NewInt(1), "unsupported arg type: uint"},
|
||||||
{"int", big.NewInt(1), "unsupported arg type: int"},
|
{"int", nil, big.NewInt(1), "unsupported arg type: int"},
|
||||||
{"uint256", big.NewInt(1), ""},
|
{"uint256", nil, big.NewInt(1), ""},
|
||||||
{"uint256[][3][]", [][3][]*big.Int{{{}}}, ""},
|
{"uint256[][3][]", nil, [][3][]*big.Int{{{}}}, ""},
|
||||||
{"uint256[][][3]", [3][][]*big.Int{{{}}}, ""},
|
{"uint256[][][3]", nil, [3][][]*big.Int{{{}}}, ""},
|
||||||
{"uint256[3][][]", [][][3]*big.Int{{{}}}, ""},
|
{"uint256[3][][]", nil, [][][3]*big.Int{{{}}}, ""},
|
||||||
{"uint256[3][3][3]", [3][3][3]*big.Int{{{}}}, ""},
|
{"uint256[3][3][3]", nil, [3][3][3]*big.Int{{{}}}, ""},
|
||||||
{"uint8[][]", [][]uint8{}, ""},
|
{"uint8[][]", nil, [][]uint8{}, ""},
|
||||||
{"int256", big.NewInt(1), ""},
|
{"int256", nil, big.NewInt(1), ""},
|
||||||
{"uint8", uint8(1), ""},
|
{"uint8", nil, uint8(1), ""},
|
||||||
{"uint16", uint16(1), ""},
|
{"uint16", nil, uint16(1), ""},
|
||||||
{"uint32", uint32(1), ""},
|
{"uint32", nil, uint32(1), ""},
|
||||||
{"uint64", uint64(1), ""},
|
{"uint64", nil, uint64(1), ""},
|
||||||
{"int8", int8(1), ""},
|
{"int8", nil, int8(1), ""},
|
||||||
{"int16", int16(1), ""},
|
{"int16", nil, int16(1), ""},
|
||||||
{"int32", int32(1), ""},
|
{"int32", nil, int32(1), ""},
|
||||||
{"int64", int64(1), ""},
|
{"int64", nil, int64(1), ""},
|
||||||
{"uint24", big.NewInt(1), ""},
|
{"uint24", nil, big.NewInt(1), ""},
|
||||||
{"uint40", big.NewInt(1), ""},
|
{"uint40", nil, big.NewInt(1), ""},
|
||||||
{"uint48", big.NewInt(1), ""},
|
{"uint48", nil, big.NewInt(1), ""},
|
||||||
{"uint56", big.NewInt(1), ""},
|
{"uint56", nil, big.NewInt(1), ""},
|
||||||
{"uint72", big.NewInt(1), ""},
|
{"uint72", nil, big.NewInt(1), ""},
|
||||||
{"uint80", big.NewInt(1), ""},
|
{"uint80", nil, big.NewInt(1), ""},
|
||||||
{"uint88", big.NewInt(1), ""},
|
{"uint88", nil, big.NewInt(1), ""},
|
||||||
{"uint96", big.NewInt(1), ""},
|
{"uint96", nil, big.NewInt(1), ""},
|
||||||
{"uint104", big.NewInt(1), ""},
|
{"uint104", nil, big.NewInt(1), ""},
|
||||||
{"uint112", big.NewInt(1), ""},
|
{"uint112", nil, big.NewInt(1), ""},
|
||||||
{"uint120", big.NewInt(1), ""},
|
{"uint120", nil, big.NewInt(1), ""},
|
||||||
{"uint128", big.NewInt(1), ""},
|
{"uint128", nil, big.NewInt(1), ""},
|
||||||
{"uint136", big.NewInt(1), ""},
|
{"uint136", nil, big.NewInt(1), ""},
|
||||||
{"uint144", big.NewInt(1), ""},
|
{"uint144", nil, big.NewInt(1), ""},
|
||||||
{"uint152", big.NewInt(1), ""},
|
{"uint152", nil, big.NewInt(1), ""},
|
||||||
{"uint160", big.NewInt(1), ""},
|
{"uint160", nil, big.NewInt(1), ""},
|
||||||
{"uint168", big.NewInt(1), ""},
|
{"uint168", nil, big.NewInt(1), ""},
|
||||||
{"uint176", big.NewInt(1), ""},
|
{"uint176", nil, big.NewInt(1), ""},
|
||||||
{"uint184", big.NewInt(1), ""},
|
{"uint184", nil, big.NewInt(1), ""},
|
||||||
{"uint192", big.NewInt(1), ""},
|
{"uint192", nil, big.NewInt(1), ""},
|
||||||
{"uint200", big.NewInt(1), ""},
|
{"uint200", nil, big.NewInt(1), ""},
|
||||||
{"uint208", big.NewInt(1), ""},
|
{"uint208", nil, big.NewInt(1), ""},
|
||||||
{"uint216", big.NewInt(1), ""},
|
{"uint216", nil, big.NewInt(1), ""},
|
||||||
{"uint224", big.NewInt(1), ""},
|
{"uint224", nil, big.NewInt(1), ""},
|
||||||
{"uint232", big.NewInt(1), ""},
|
{"uint232", nil, big.NewInt(1), ""},
|
||||||
{"uint240", big.NewInt(1), ""},
|
{"uint240", nil, big.NewInt(1), ""},
|
||||||
{"uint248", big.NewInt(1), ""},
|
{"uint248", nil, big.NewInt(1), ""},
|
||||||
{"int24", big.NewInt(1), ""},
|
{"int24", nil, big.NewInt(1), ""},
|
||||||
{"int40", big.NewInt(1), ""},
|
{"int40", nil, big.NewInt(1), ""},
|
||||||
{"int48", big.NewInt(1), ""},
|
{"int48", nil, big.NewInt(1), ""},
|
||||||
{"int56", big.NewInt(1), ""},
|
{"int56", nil, big.NewInt(1), ""},
|
||||||
{"int72", big.NewInt(1), ""},
|
{"int72", nil, big.NewInt(1), ""},
|
||||||
{"int80", big.NewInt(1), ""},
|
{"int80", nil, big.NewInt(1), ""},
|
||||||
{"int88", big.NewInt(1), ""},
|
{"int88", nil, big.NewInt(1), ""},
|
||||||
{"int96", big.NewInt(1), ""},
|
{"int96", nil, big.NewInt(1), ""},
|
||||||
{"int104", big.NewInt(1), ""},
|
{"int104", nil, big.NewInt(1), ""},
|
||||||
{"int112", big.NewInt(1), ""},
|
{"int112", nil, big.NewInt(1), ""},
|
||||||
{"int120", big.NewInt(1), ""},
|
{"int120", nil, big.NewInt(1), ""},
|
||||||
{"int128", big.NewInt(1), ""},
|
{"int128", nil, big.NewInt(1), ""},
|
||||||
{"int136", big.NewInt(1), ""},
|
{"int136", nil, big.NewInt(1), ""},
|
||||||
{"int144", big.NewInt(1), ""},
|
{"int144", nil, big.NewInt(1), ""},
|
||||||
{"int152", big.NewInt(1), ""},
|
{"int152", nil, big.NewInt(1), ""},
|
||||||
{"int160", big.NewInt(1), ""},
|
{"int160", nil, big.NewInt(1), ""},
|
||||||
{"int168", big.NewInt(1), ""},
|
{"int168", nil, big.NewInt(1), ""},
|
||||||
{"int176", big.NewInt(1), ""},
|
{"int176", nil, big.NewInt(1), ""},
|
||||||
{"int184", big.NewInt(1), ""},
|
{"int184", nil, big.NewInt(1), ""},
|
||||||
{"int192", big.NewInt(1), ""},
|
{"int192", nil, big.NewInt(1), ""},
|
||||||
{"int200", big.NewInt(1), ""},
|
{"int200", nil, big.NewInt(1), ""},
|
||||||
{"int208", big.NewInt(1), ""},
|
{"int208", nil, big.NewInt(1), ""},
|
||||||
{"int216", big.NewInt(1), ""},
|
{"int216", nil, big.NewInt(1), ""},
|
||||||
{"int224", big.NewInt(1), ""},
|
{"int224", nil, big.NewInt(1), ""},
|
||||||
{"int232", big.NewInt(1), ""},
|
{"int232", nil, big.NewInt(1), ""},
|
||||||
{"int240", big.NewInt(1), ""},
|
{"int240", nil, big.NewInt(1), ""},
|
||||||
{"int248", big.NewInt(1), ""},
|
{"int248", nil, big.NewInt(1), ""},
|
||||||
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
{"uint30", nil, uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||||
{"uint8", uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
{"uint8", nil, uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
||||||
{"uint8", uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
{"uint8", nil, uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
||||||
{"uint8", uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
{"uint8", nil, uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
||||||
{"uint8", int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
{"uint8", nil, int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
||||||
{"uint8", int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
{"uint8", nil, int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
||||||
{"uint8", int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
{"uint8", nil, int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
||||||
{"uint8", int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
{"uint8", nil, int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
||||||
{"uint16", uint16(1), ""},
|
{"uint16", nil, uint16(1), ""},
|
||||||
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
{"uint16", nil, uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||||
{"uint16[]", []uint16{1, 2, 3}, ""},
|
{"uint16[]", nil, []uint16{1, 2, 3}, ""},
|
||||||
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
{"uint16[]", nil, [3]uint16{1, 2, 3}, ""},
|
||||||
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
|
{"uint16[]", nil, []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]", nil, [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]", nil, [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
{"uint16[3]", nil, []uint16{1, 2, 3}, ""},
|
||||||
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
{"uint16[3]", nil, []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
{"address[]", []common.Address{{1}}, ""},
|
{"address[]", nil, []common.Address{{1}}, ""},
|
||||||
{"address[1]", []common.Address{{1}}, ""},
|
{"address[1]", nil, []common.Address{{1}}, ""},
|
||||||
{"address[1]", [1]common.Address{{1}}, ""},
|
{"address[1]", nil, [1]common.Address{{1}}, ""},
|
||||||
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
{"address[2]", nil, [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||||
{"bytes32", [32]byte{}, ""},
|
{"bytes32", nil, [32]byte{}, ""},
|
||||||
{"bytes31", [31]byte{}, ""},
|
{"bytes31", nil, [31]byte{}, ""},
|
||||||
{"bytes30", [30]byte{}, ""},
|
{"bytes30", nil, [30]byte{}, ""},
|
||||||
{"bytes29", [29]byte{}, ""},
|
{"bytes29", nil, [29]byte{}, ""},
|
||||||
{"bytes28", [28]byte{}, ""},
|
{"bytes28", nil, [28]byte{}, ""},
|
||||||
{"bytes27", [27]byte{}, ""},
|
{"bytes27", nil, [27]byte{}, ""},
|
||||||
{"bytes26", [26]byte{}, ""},
|
{"bytes26", nil, [26]byte{}, ""},
|
||||||
{"bytes25", [25]byte{}, ""},
|
{"bytes25", nil, [25]byte{}, ""},
|
||||||
{"bytes24", [24]byte{}, ""},
|
{"bytes24", nil, [24]byte{}, ""},
|
||||||
{"bytes23", [23]byte{}, ""},
|
{"bytes23", nil, [23]byte{}, ""},
|
||||||
{"bytes22", [22]byte{}, ""},
|
{"bytes22", nil, [22]byte{}, ""},
|
||||||
{"bytes21", [21]byte{}, ""},
|
{"bytes21", nil, [21]byte{}, ""},
|
||||||
{"bytes20", [20]byte{}, ""},
|
{"bytes20", nil, [20]byte{}, ""},
|
||||||
{"bytes19", [19]byte{}, ""},
|
{"bytes19", nil, [19]byte{}, ""},
|
||||||
{"bytes18", [18]byte{}, ""},
|
{"bytes18", nil, [18]byte{}, ""},
|
||||||
{"bytes17", [17]byte{}, ""},
|
{"bytes17", nil, [17]byte{}, ""},
|
||||||
{"bytes16", [16]byte{}, ""},
|
{"bytes16", nil, [16]byte{}, ""},
|
||||||
{"bytes15", [15]byte{}, ""},
|
{"bytes15", nil, [15]byte{}, ""},
|
||||||
{"bytes14", [14]byte{}, ""},
|
{"bytes14", nil, [14]byte{}, ""},
|
||||||
{"bytes13", [13]byte{}, ""},
|
{"bytes13", nil, [13]byte{}, ""},
|
||||||
{"bytes12", [12]byte{}, ""},
|
{"bytes12", nil, [12]byte{}, ""},
|
||||||
{"bytes11", [11]byte{}, ""},
|
{"bytes11", nil, [11]byte{}, ""},
|
||||||
{"bytes10", [10]byte{}, ""},
|
{"bytes10", nil, [10]byte{}, ""},
|
||||||
{"bytes9", [9]byte{}, ""},
|
{"bytes9", nil, [9]byte{}, ""},
|
||||||
{"bytes8", [8]byte{}, ""},
|
{"bytes8", nil, [8]byte{}, ""},
|
||||||
{"bytes7", [7]byte{}, ""},
|
{"bytes7", nil, [7]byte{}, ""},
|
||||||
{"bytes6", [6]byte{}, ""},
|
{"bytes6", nil, [6]byte{}, ""},
|
||||||
{"bytes5", [5]byte{}, ""},
|
{"bytes5", nil, [5]byte{}, ""},
|
||||||
{"bytes4", [4]byte{}, ""},
|
{"bytes4", nil, [4]byte{}, ""},
|
||||||
{"bytes3", [3]byte{}, ""},
|
{"bytes3", nil, [3]byte{}, ""},
|
||||||
{"bytes2", [2]byte{}, ""},
|
{"bytes2", nil, [2]byte{}, ""},
|
||||||
{"bytes1", [1]byte{}, ""},
|
{"bytes1", nil, [1]byte{}, ""},
|
||||||
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
{"bytes32", nil, [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||||
{"bytes32", common.Hash{1}, ""},
|
{"bytes32", nil, common.Hash{1}, ""},
|
||||||
{"bytes31", common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
|
{"bytes31", nil, 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"},
|
{"bytes31", nil, [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||||
{"bytes", []byte{0, 1}, ""},
|
{"bytes", nil, []byte{0, 1}, ""},
|
||||||
{"bytes", [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
|
{"bytes", nil, [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"},
|
{"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"},
|
||||||
{"string", "hello world", ""},
|
{"string", nil, "hello world", ""},
|
||||||
{"string", string(""), ""},
|
{"string", nil, string(""), ""},
|
||||||
{"string", []byte{}, "abi: cannot use slice as type string as argument"},
|
{"string", nil, []byte{}, "abi: cannot use slice as type string as argument"},
|
||||||
{"bytes32[]", [][32]byte{{}}, ""},
|
{"bytes32[]", nil, [][32]byte{{}}, ""},
|
||||||
{"function", [24]byte{}, ""},
|
{"function", nil, [24]byte{}, ""},
|
||||||
{"bytes20", common.Address{}, ""},
|
{"bytes20", nil, common.Address{}, ""},
|
||||||
{"address", [20]byte{}, ""},
|
{"address", nil, [20]byte{}, ""},
|
||||||
{"address", common.Address{}, ""},
|
{"address", nil, common.Address{}, ""},
|
||||||
|
{"bytes32[]]", nil, "", "invalid arg type in abi"},
|
||||||
|
{"invalidType", nil, "", "unsupported arg type: invalidType"},
|
||||||
|
{"invalidSlice[]", nil, "", "unsupported arg type: invalidSlice"},
|
||||||
|
// simple tuple
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{}, ""},
|
||||||
|
// tuple slice
|
||||||
|
{"tuple[]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{}, ""},
|
||||||
|
// tuple array
|
||||||
|
{"tuple[2]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""},
|
||||||
} {
|
} {
|
||||||
typ, err := NewType(test.typ)
|
typ, err := NewType(test.typ, test.components)
|
||||||
if err != nil && len(test.err) == 0 {
|
if err != nil && len(test.err) == 0 {
|
||||||
t.Fatal("unexpected parse error:", err)
|
t.Fatal("unexpected parse error:", err)
|
||||||
} else if err != nil && len(test.err) != 0 {
|
} else if err != nil && len(test.err) != 0 {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -25,8 +25,17 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
maxUint256 = big.NewInt(0).Add(
|
||||||
|
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil),
|
||||||
|
big.NewInt(-1))
|
||||||
|
maxInt256 = big.NewInt(0).Add(
|
||||||
|
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil),
|
||||||
|
big.NewInt(-1))
|
||||||
|
)
|
||||||
|
|
||||||
// reads the integer based on its kind
|
// reads the integer based on its kind
|
||||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Uint8:
|
case reflect.Uint8:
|
||||||
return b[len(b)-1]
|
return b[len(b)-1]
|
||||||
|
|
@ -45,7 +54,20 @@ func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
||||||
default:
|
default:
|
||||||
return new(big.Int).SetBytes(b)
|
// the only case lefts for integer is int256/uint256.
|
||||||
|
// big.SetBytes can't tell if a number is negative, positive on itself.
|
||||||
|
// On EVM, if the returned number > max int256, it is negative.
|
||||||
|
ret := new(big.Int).SetBytes(b)
|
||||||
|
if typ == UintTy {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.Cmp(maxInt256) > 0 {
|
||||||
|
ret.Add(maxUint256, big.NewInt(0).Neg(ret))
|
||||||
|
ret.Add(ret, big.NewInt(1))
|
||||||
|
ret.Neg(ret)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,13 +117,15 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||||
|
|
||||||
// iteratively unpack elements
|
// iteratively unpack elements
|
||||||
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
|
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) {
|
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)
|
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
|
// this value will become our slice or our array, depending on the type
|
||||||
var refSlice reflect.Value
|
var refSlice reflect.Value
|
||||||
slice := output[start : start+size*32]
|
|
||||||
|
|
||||||
if t.T == SliceTy {
|
if t.T == SliceTy {
|
||||||
// declare our slice
|
// declare our slice
|
||||||
|
|
@ -113,15 +137,16 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
|
||||||
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, j := start, 0; j*32 < len(slice); i, j = i+32, j+1 {
|
// Arrays have packed elements, resulting in longer unpack steps.
|
||||||
// this corrects the arrangement so that we get all the underlying array values
|
// Slices have just 32 bytes per element (pointing to the contents).
|
||||||
if t.Elem.T == ArrayTy && j != 0 {
|
elemSize := getTypeSize(*t.Elem)
|
||||||
i = start + t.Elem.Size*32*j
|
|
||||||
}
|
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
|
||||||
inter, err := toGoType(i, *t.Elem, output)
|
inter, err := toGoType(i, *t.Elem, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the item to our reflect slice
|
// append the item to our reflect slice
|
||||||
refSlice.Index(j).Set(reflect.ValueOf(inter))
|
refSlice.Index(j).Set(reflect.ValueOf(inter))
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +155,36 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
|
||||||
return refSlice.Interface(), nil
|
return refSlice.Interface(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forTupleUnpack(t Type, output []byte) (interface{}, error) {
|
||||||
|
retval := reflect.New(t.Type).Elem()
|
||||||
|
virtualArgs := 0
|
||||||
|
for index, elem := range t.TupleElems {
|
||||||
|
marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output)
|
||||||
|
if elem.T == ArrayTy && !isDynamicType(*elem) {
|
||||||
|
// If we have a static array, like [3]uint256, these are coded as
|
||||||
|
// just like uint256,uint256,uint256.
|
||||||
|
// 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 += getTypeSize(*elem)/32 - 1
|
||||||
|
} else if elem.T == TupleTy && !isDynamicType(*elem) {
|
||||||
|
// If we have a static tuple, like (uint256, bool, uint256), these are
|
||||||
|
// coded as just like uint256,bool,uint256
|
||||||
|
virtualArgs += getTypeSize(*elem)/32 - 1
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
retval.Field(index).Set(reflect.ValueOf(marshalledValue))
|
||||||
|
}
|
||||||
|
return retval.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// toGoType parses the output bytes and recursively assigns the value of these bytes
|
// toGoType parses the output bytes and recursively assigns the value of these bytes
|
||||||
// into a go type with accordance with the ABI spec.
|
// into a go type with accordance with the ABI spec.
|
||||||
func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
|
|
@ -139,13 +194,13 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
returnOutput []byte
|
returnOutput []byte
|
||||||
begin, end int
|
begin, length int
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
// if we require a length prefix, find the beginning word and size returned.
|
// if we require a length prefix, find the beginning word and size returned.
|
||||||
if t.requiresLengthPrefix() {
|
if t.requiresLengthPrefix() {
|
||||||
begin, end, err = lengthPrefixPointsTo(index, output)
|
begin, length, err = lengthPrefixPointsTo(index, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -154,14 +209,28 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t.T {
|
switch t.T {
|
||||||
|
case TupleTy:
|
||||||
|
if isDynamicType(t) {
|
||||||
|
begin, err := tuplePointsTo(index, output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return forTupleUnpack(t, output[begin:])
|
||||||
|
} else {
|
||||||
|
return forTupleUnpack(t, output[index:])
|
||||||
|
}
|
||||||
case SliceTy:
|
case SliceTy:
|
||||||
return forEachUnpack(t, output, begin, end)
|
return forEachUnpack(t, output[begin:], 0, length)
|
||||||
case ArrayTy:
|
case ArrayTy:
|
||||||
return forEachUnpack(t, output, index, t.Size)
|
if isDynamicType(*t.Elem) {
|
||||||
|
offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]))
|
||||||
|
return forEachUnpack(t, output[offset:], 0, t.Size)
|
||||||
|
}
|
||||||
|
return forEachUnpack(t, output[index:], 0, t.Size)
|
||||||
case StringTy: // variable arrays are written at the end of the return bytes
|
case StringTy: // variable arrays are written at the end of the return bytes
|
||||||
return string(output[begin : begin+end]), nil
|
return string(output[begin : begin+length]), nil
|
||||||
case IntTy, UintTy:
|
case IntTy, UintTy:
|
||||||
return readInteger(t.Kind, returnOutput), nil
|
return readInteger(t.T, t.Kind, returnOutput), nil
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
return readBool(returnOutput)
|
return readBool(returnOutput)
|
||||||
case AddressTy:
|
case AddressTy:
|
||||||
|
|
@ -169,7 +238,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
case HashTy:
|
case HashTy:
|
||||||
return common.BytesToHash(returnOutput), nil
|
return common.BytesToHash(returnOutput), nil
|
||||||
case BytesTy:
|
case BytesTy:
|
||||||
return output[begin : begin+end], nil
|
return output[begin : begin+length], nil
|
||||||
case FixedBytesTy:
|
case FixedBytesTy:
|
||||||
return readFixedBytes(t, returnOutput)
|
return readFixedBytes(t, returnOutput)
|
||||||
case FunctionTy:
|
case FunctionTy:
|
||||||
|
|
@ -181,16 +250,46 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
|
||||||
|
|
||||||
// interprets a 32 byte slice as an offset and then determines which indice to look to decode the type.
|
// 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) {
|
func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err error) {
|
||||||
offset := int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
bigOffsetEnd := big.NewInt(0).SetBytes(output[index : index+32])
|
||||||
if offset+32 > len(output) {
|
bigOffsetEnd.Add(bigOffsetEnd, common.Big32)
|
||||||
return 0, 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32)
|
outputLength := big.NewInt(int64(len(output)))
|
||||||
}
|
|
||||||
length = int(binary.BigEndian.Uint64(output[offset+24 : offset+32]))
|
|
||||||
if offset+32+length > len(output) {
|
|
||||||
return 0, 0, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+length)
|
|
||||||
}
|
|
||||||
start = offset + 32
|
|
||||||
|
|
||||||
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tuplePointsTo resolves the location reference for dynamic tuple.
|
||||||
|
func tuplePointsTo(index int, output []byte) (start int, err error) {
|
||||||
|
offset := big.NewInt(0).SetBytes(output[index : index+32])
|
||||||
|
outputLen := big.NewInt(int64(len(output)))
|
||||||
|
|
||||||
|
if offset.Cmp(big.NewInt(int64(len(output)))) > 0 {
|
||||||
|
return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen)
|
||||||
|
}
|
||||||
|
if offset.BitLen() > 63 {
|
||||||
|
return 0, fmt.Errorf("abi offset larger than int64: %v", offset)
|
||||||
|
}
|
||||||
|
return int(offset.Uint64()), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -56,6 +56,23 @@ var unpackTests = []unpackTest{
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
want: true,
|
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"}]`,
|
def: `[{"type": "uint32"}]`,
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
|
@ -100,6 +117,11 @@ var unpackTests = []unpackTest{
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
want: big.NewInt(1),
|
want: big.NewInt(1),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "int256"}]`,
|
||||||
|
enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
want: big.NewInt(-1),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "address"}]`,
|
def: `[{"type": "address"}]`,
|
||||||
enc: "0000000000000000000000000100000000000000000000000000000000000000",
|
enc: "0000000000000000000000000100000000000000000000000000000000000000",
|
||||||
|
|
@ -130,7 +152,7 @@ var unpackTests = []unpackTest{
|
||||||
{
|
{
|
||||||
def: `[{"type": "bytes32"}]`,
|
def: `[{"type": "bytes32"}]`,
|
||||||
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
enc: "0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
want: common.HexToHash("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"}]`,
|
def: `[{"type": "function"}]`,
|
||||||
|
|
@ -151,9 +173,14 @@ var unpackTests = []unpackTest{
|
||||||
// multi dimensional, if these pass, all types that don't require length prefix should pass
|
// multi dimensional, if these pass, all types that don't require length prefix should pass
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint8[][]"}]`,
|
def: `[{"type": "uint8[][]"}]`,
|
||||||
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000E0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
want: [][]uint8{{1, 2}, {1, 2}},
|
want: [][]uint8{{1, 2}, {1, 2}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[][]"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
want: [][]uint8{{1, 2}, {1, 2, 3}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint8[2][2]"}]`,
|
def: `[{"type": "uint8[2][2]"}]`,
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
|
@ -161,7 +188,7 @@ var unpackTests = []unpackTest{
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint8[][2]"}]`,
|
def: `[{"type": "uint8[][2]"}]`,
|
||||||
enc: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
|
||||||
want: [2][]uint8{{1}, {1}},
|
want: [2][]uint8{{1}, {1}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -169,6 +196,11 @@ var unpackTests = []unpackTest{
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
want: [][2]uint8{{1, 2}},
|
want: [][2]uint8{{1, 2}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint8[2][]"}]`,
|
||||||
|
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: [][2]uint8{{1, 2}, {1, 2}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint16[]"}]`,
|
def: `[{"type": "uint16[]"}]`,
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
|
@ -189,6 +221,11 @@ var unpackTests = []unpackTest{
|
||||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
want: [2]uint32{1, 2},
|
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[]"}]`,
|
def: `[{"type": "uint64[]"}]`,
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
|
@ -209,6 +246,26 @@ var unpackTests = []unpackTest{
|
||||||
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
|
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
|
||||||
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
|
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string[4]"}]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b476f2d657468657265756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000",
|
||||||
|
want: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "string[]"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b676f2d657468657265756d000000000000000000000000000000000000000000",
|
||||||
|
want: []string{"Ethereum", "go-ethereum"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "bytes[]"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003f0f0f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0f0f00000000000000000000000000000000000000000000000000000000000",
|
||||||
|
want: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"type": "uint256[2][][]"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8",
|
||||||
|
want: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "int8[]"}]`,
|
def: `[{"type": "int8[]"}]`,
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
|
@ -268,6 +325,53 @@ var unpackTests = []unpackTest{
|
||||||
Int2 *big.Int
|
Int2 *big.Int
|
||||||
}{big.NewInt(1), big.NewInt(2)},
|
}{big.NewInt(1), big.NewInt(2)},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{"name":"int_one","type":"int256"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
}{big.NewInt(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"name":"int__one","type":"int256"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
}{big.NewInt(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"name":"int_one_","type":"int256"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
}{big.NewInt(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
Intone *big.Int
|
||||||
|
}{big.NewInt(1), big.NewInt(2)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"name":"___","type":"int256"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: struct {
|
||||||
|
IntOne *big.Int
|
||||||
|
Intone *big.Int
|
||||||
|
}{},
|
||||||
|
err: "abi: purely underscored output cannot unpack to struct",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{"name":"int_one","type":"int256"},{"name":"IntOne","type":"int256"}]`,
|
||||||
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
want: struct {
|
||||||
|
Int1 *big.Int
|
||||||
|
Int2 *big.Int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple outputs mapping to the same struct field 'IntOne'",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`,
|
def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`,
|
||||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||||
|
|
@ -332,6 +436,55 @@ func TestUnpack(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpackSetDynamicArrayOutput(t *testing.T) {
|
||||||
|
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
marshalledReturn32 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783132333435363738393000000000000000000000000000000000000000003078303938373635343332310000000000000000000000000000000000000000")
|
||||||
|
marshalledReturn15 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783031323334350000000000000000000000000000000000000000000000003078393837363534000000000000000000000000000000000000000000000000")
|
||||||
|
|
||||||
|
out32 [][32]byte
|
||||||
|
out15 [][15]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
// test 32
|
||||||
|
err = abi.Unpack(&out32, "testDynamicFixedBytes32", marshalledReturn32)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(out32) != 2 {
|
||||||
|
t.Fatalf("expected array with 2 values, got %d", len(out32))
|
||||||
|
}
|
||||||
|
expected := common.Hex2Bytes("3078313233343536373839300000000000000000000000000000000000000000")
|
||||||
|
if !bytes.Equal(out32[0][:], expected) {
|
||||||
|
t.Errorf("expected %x, got %x\n", expected, out32[0])
|
||||||
|
}
|
||||||
|
expected = common.Hex2Bytes("3078303938373635343332310000000000000000000000000000000000000000")
|
||||||
|
if !bytes.Equal(out32[1][:], expected) {
|
||||||
|
t.Errorf("expected %x, got %x\n", expected, out32[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// test 15
|
||||||
|
err = abi.Unpack(&out15, "testDynamicFixedBytes32", marshalledReturn15)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(out15) != 2 {
|
||||||
|
t.Fatalf("expected array with 2 values, got %d", len(out15))
|
||||||
|
}
|
||||||
|
expected = common.Hex2Bytes("307830313233343500000000000000")
|
||||||
|
if !bytes.Equal(out15[0][:], expected) {
|
||||||
|
t.Errorf("expected %x, got %x\n", expected, out15[0])
|
||||||
|
}
|
||||||
|
expected = common.Hex2Bytes("307839383736353400000000000000")
|
||||||
|
if !bytes.Equal(out15[1][:], expected) {
|
||||||
|
t.Errorf("expected %x, got %x\n", expected, out15[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type methodMultiOutput struct {
|
type methodMultiOutput struct {
|
||||||
Int *big.Int
|
Int *big.Int
|
||||||
String string
|
String string
|
||||||
|
|
@ -359,6 +512,11 @@ func TestMethodMultiReturn(t *testing.T) {
|
||||||
Int *big.Int
|
Int *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newInterfaceSlice := func(len int) interface{} {
|
||||||
|
slice := make([]interface{}, len)
|
||||||
|
return &slice
|
||||||
|
}
|
||||||
|
|
||||||
abi, data, expected := methodMultiReturn(require.New(t))
|
abi, data, expected := methodMultiReturn(require.New(t))
|
||||||
bigint := new(big.Int)
|
bigint := new(big.Int)
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
|
|
@ -386,6 +544,16 @@ func TestMethodMultiReturn(t *testing.T) {
|
||||||
&[2]interface{}{&expected.Int, &expected.String},
|
&[2]interface{}{&expected.Int, &expected.String},
|
||||||
"",
|
"",
|
||||||
"Can unpack into an array",
|
"Can unpack into an array",
|
||||||
|
}, {
|
||||||
|
&[2]interface{}{},
|
||||||
|
&[2]interface{}{expected.Int, expected.String},
|
||||||
|
"",
|
||||||
|
"Can unpack into interface array",
|
||||||
|
}, {
|
||||||
|
newInterfaceSlice(2),
|
||||||
|
&[]interface{}{expected.Int, expected.String},
|
||||||
|
"",
|
||||||
|
"Can unpack into interface slice",
|
||||||
}, {
|
}, {
|
||||||
&[]interface{}{new(int), new(int)},
|
&[]interface{}{new(int), new(int)},
|
||||||
&[]interface{}{&expected.Int, &expected.String},
|
&[]interface{}{&expected.Int, &expected.String},
|
||||||
|
|
@ -435,6 +603,108 @@ func TestMultiReturnWithArray(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultiReturnWithStringArray(t *testing.T) {
|
||||||
|
const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000"))
|
||||||
|
temp, _ := big.NewInt(0).SetString("30000000000000000000", 10)
|
||||||
|
ret1, ret1Exp := new([3]*big.Int), [3]*big.Int{big.NewInt(1545304298), big.NewInt(6), temp}
|
||||||
|
ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f")
|
||||||
|
ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"}
|
||||||
|
ret4, ret4Exp := new(bool), false
|
||||||
|
if err := abi.Unpack(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*ret1, ret1Exp) {
|
||||||
|
t.Error("big.Int array result", *ret1, "!= Expected", ret1Exp)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*ret2, ret2Exp) {
|
||||||
|
t.Error("address result", *ret2, "!= Expected", ret2Exp)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*ret3, ret3Exp) {
|
||||||
|
t.Error("string array result", *ret3, "!= Expected", ret3Exp)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*ret4, ret4Exp) {
|
||||||
|
t.Error("bool result", *ret4, "!= Expected", ret4Exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiReturnWithStringSlice(t *testing.T) {
|
||||||
|
const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0] offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000120")) // output[1] offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[0] length
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0][0] offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // output[0][1] offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) // output[0][0] length
|
||||||
|
buff.Write(common.Hex2Bytes("657468657265756d000000000000000000000000000000000000000000000000")) // output[0][0] value
|
||||||
|
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000b")) // output[0][1] length
|
||||||
|
buff.Write(common.Hex2Bytes("676f2d657468657265756d000000000000000000000000000000000000000000")) // output[0][1] value
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[1] length
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064")) // output[1][0] value
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value
|
||||||
|
ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"}
|
||||||
|
ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)}
|
||||||
|
if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*ret1, ret1Exp) {
|
||||||
|
t.Error("string slice result", *ret1, "!= Expected", ret1Exp)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*ret2, ret2Exp) {
|
||||||
|
t.Error("uint256 slice 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) {
|
func TestUnmarshal(t *testing.T) {
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
||||||
|
|
@ -683,3 +953,175 @@ func TestUnmarshal(t *testing.T) {
|
||||||
t.Fatal("expected error:", err)
|
t.Fatal("expected error:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpackTuple(t *testing.T) {
|
||||||
|
const simpleTuple = `[{"name":"tuple","constant":false,"outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
|
||||||
|
abi, err := JSON(strings.NewReader(simpleTuple))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1
|
||||||
|
buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1
|
||||||
|
|
||||||
|
v := struct {
|
||||||
|
Ret struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}
|
||||||
|
}{Ret: struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{new(big.Int), new(big.Int)}}
|
||||||
|
|
||||||
|
err = abi.Unpack(&v, "tuple", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
if v.Ret.A.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.Ret.A)
|
||||||
|
}
|
||||||
|
if v.Ret.B.Cmp(big.NewInt(-1)) != 0 {
|
||||||
|
t.Errorf("unexpected value unpacked: want %x, got %x", v.Ret.B, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test nested tuple
|
||||||
|
const nestedTuple = `[{"name":"tuple","constant":false,"outputs":[
|
||||||
|
{"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]},
|
||||||
|
{"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]},
|
||||||
|
{"type":"uint256","name":"a"}
|
||||||
|
]}]`
|
||||||
|
|
||||||
|
abi, err = JSON(strings.NewReader(nestedTuple))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // s offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")) // t.X = 0
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // t.Y = 1
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // a = 1
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.A = 1
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")) // s.B offset
|
||||||
|
buff.Write(common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0")) // s.C offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B length
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.B[0] = 1
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B[0] = 2
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C length
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[0].X
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[0].Y
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[1].X
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[1].Y
|
||||||
|
|
||||||
|
type T struct {
|
||||||
|
X *big.Int `abi:"x"`
|
||||||
|
Z *big.Int `abi:"y"` // Test whether the abi tag works.
|
||||||
|
}
|
||||||
|
|
||||||
|
type S struct {
|
||||||
|
A *big.Int
|
||||||
|
B []*big.Int
|
||||||
|
C []T
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ret struct {
|
||||||
|
FieldS S `abi:"s"`
|
||||||
|
FieldT T `abi:"t"`
|
||||||
|
A *big.Int
|
||||||
|
}
|
||||||
|
var ret Ret
|
||||||
|
var expected = Ret{
|
||||||
|
FieldS: S{
|
||||||
|
A: big.NewInt(1),
|
||||||
|
B: []*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
C: []T{
|
||||||
|
{big.NewInt(1), big.NewInt(2)},
|
||||||
|
{big.NewInt(2), big.NewInt(1)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FieldT: T{
|
||||||
|
big.NewInt(0), big.NewInt(1),
|
||||||
|
},
|
||||||
|
A: big.NewInt(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = abi.Unpack(&ret, "tuple", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if reflect.DeepEqual(ret, expected) {
|
||||||
|
t.Error("unexpected unpack value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,14 @@
|
||||||
package accounts
|
package accounts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
ethereum "github.com/ethereum/go-ethereum"
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Account represents an Ethereum account located at a specific location defined
|
// Account represents an Ethereum account located at a specific location defined
|
||||||
|
|
@ -33,6 +35,13 @@ type Account struct {
|
||||||
URL URL `json:"url"` // Optional resource locator within a backend
|
URL URL `json:"url"` // Optional resource locator within a backend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MimetypeTextWithValidator = "text/validator"
|
||||||
|
MimetypeTypedData = "data/typed"
|
||||||
|
MimetypeClique = "application/x-clique-header"
|
||||||
|
MimetypeTextPlain = "text/plain"
|
||||||
|
)
|
||||||
|
|
||||||
// Wallet represents a software or hardware wallet that might contain one or more
|
// Wallet represents a software or hardware wallet that might contain one or more
|
||||||
// accounts (derived from the same seed).
|
// accounts (derived from the same seed).
|
||||||
type Wallet interface {
|
type Wallet interface {
|
||||||
|
|
@ -87,8 +96,26 @@ type Wallet interface {
|
||||||
// chain state reader.
|
// chain state reader.
|
||||||
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
|
SelfDerive(base DerivationPath, chain ethereum.ChainStateReader)
|
||||||
|
|
||||||
// SignHash requests the wallet to sign the given hash.
|
// SignData requests the wallet to sign the hash of the given data
|
||||||
|
// 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 SignDataWithPassphrase, or by other means (e.g. unlock
|
||||||
|
// the account in a keystore).
|
||||||
|
SignData(account Account, mimeType string, data []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignDataWithPassphrase is identical to SignData, but also takes a password
|
||||||
|
// NOTE: there's an chance that an erroneous call might mistake the two strings, and
|
||||||
|
// supply password in the mimetype field, or vice versa. Thus, an implementation
|
||||||
|
// should never echo the mimetype or return the mimetype in the error-response
|
||||||
|
SignDataWithPassphrase(account Account, passphrase, mimeType string, data []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignText requests the wallet to sign the hash of a given piece of data, prefixed
|
||||||
|
// by the Ethereum prefix scheme
|
||||||
// It looks up the account specified either solely via its address contained within,
|
// 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.
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
//
|
//
|
||||||
|
|
@ -98,7 +125,10 @@ type Wallet interface {
|
||||||
// about which fields or actions are needed. The user may retry by providing
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock
|
||||||
// the account in a keystore).
|
// the account in a keystore).
|
||||||
SignHash(account Account, hash []byte) ([]byte, error)
|
SignText(account Account, text []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SignTextWithPassphrase is identical to Signtext, but also takes a password
|
||||||
|
SignTextWithPassphrase(account Account, passphrase string, hash []byte) ([]byte, error)
|
||||||
|
|
||||||
// SignTx requests the wallet to sign the given transaction.
|
// SignTx requests the wallet to sign the given transaction.
|
||||||
//
|
//
|
||||||
|
|
@ -106,25 +136,14 @@ type Wallet interface {
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
// 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.
|
// 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),
|
// a password to decrypt the account, or a PIN code to verify the transaction),
|
||||||
// an AuthNeededError instance will be returned, containing infos for the user
|
// an AuthNeededError instance will be returned, containing infos for the user
|
||||||
// about which fields or actions are needed. The user may retry by providing
|
// 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 needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
||||||
// the account in a keystore).
|
// the account in a keystore).
|
||||||
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
SignTx(account Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||||
|
|
||||||
// SignHashWithPassphrase requests the wallet to sign the given hash with the
|
// SignTxWithPassphrase is identical to SignTx, but also takes a password
|
||||||
// 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)
|
SignTxWithPassphrase(account Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,6 +167,32 @@ type Backend interface {
|
||||||
Subscribe(sink chan<- WalletEvent) event.Subscription
|
Subscribe(sink chan<- WalletEvent) event.Subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextHash is a helper function that calculates a hash for the given message that can be
|
||||||
|
// safely used to calculate a signature from.
|
||||||
|
//
|
||||||
|
// The hash is calulcated as
|
||||||
|
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||||
|
//
|
||||||
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
|
func TextHash(data []byte) []byte {
|
||||||
|
hash, _ := TextAndHash(data)
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextAndHash is a helper function that calculates a hash for the given message that can be
|
||||||
|
// safely used to calculate a signature from.
|
||||||
|
//
|
||||||
|
// The hash is calulcated as
|
||||||
|
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||||
|
//
|
||||||
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
|
func TextAndHash(data []byte) ([]byte, string) {
|
||||||
|
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), string(data))
|
||||||
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
|
hasher.Write([]byte(msg))
|
||||||
|
return hasher.Sum(nil), msg
|
||||||
|
}
|
||||||
|
|
||||||
// WalletEventType represents the different event types that can be fired by
|
// WalletEventType represents the different event types that can be fired by
|
||||||
// the wallet subscription subsystem.
|
// the wallet subscription subsystem.
|
||||||
type WalletEventType int
|
type WalletEventType int
|
||||||
|
|
|
||||||
32
accounts/accounts_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTextHash(t *testing.T) {
|
||||||
|
hash := TextHash([]byte("Hello Joe"))
|
||||||
|
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
||||||
|
if !bytes.Equal(hash, want) {
|
||||||
|
t.Fatalf("wrong hash: %x", hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
228
accounts/external/backend.go
vendored
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"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/event"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalBackend struct {
|
||||||
|
signers []accounts.Wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExternalBackend) Wallets() []accounts.Wallet {
|
||||||
|
return eb.signers
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExternalBackend(endpoint string) (*ExternalBackend, error) {
|
||||||
|
signer, err := NewExternalSigner(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ExternalBackend{
|
||||||
|
signers: []accounts.Wallet{signer},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
|
<-quit
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalSigner provides an API to interact with an external signer (clef)
|
||||||
|
// It proxies request to the external signer while forwarding relevant
|
||||||
|
// request headers
|
||||||
|
type ExternalSigner struct {
|
||||||
|
client *rpc.Client
|
||||||
|
endpoint string
|
||||||
|
status string
|
||||||
|
cacheMu sync.RWMutex
|
||||||
|
cache []accounts.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExternalSigner(endpoint string) (*ExternalSigner, error) {
|
||||||
|
client, err := rpc.Dial(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extsigner := &ExternalSigner{
|
||||||
|
client: client,
|
||||||
|
endpoint: endpoint,
|
||||||
|
}
|
||||||
|
// Check if reachable
|
||||||
|
version, err := extsigner.pingVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extsigner.status = fmt.Sprintf("ok [version=%v]", version)
|
||||||
|
return extsigner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) URL() accounts.URL {
|
||||||
|
return accounts.URL{
|
||||||
|
Scheme: "extapi",
|
||||||
|
Path: api.endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Status() (string, error) {
|
||||||
|
return api.status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Open(passphrase string) error {
|
||||||
|
return fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Close() error {
|
||||||
|
return fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Accounts() []accounts.Account {
|
||||||
|
var accnts []accounts.Account
|
||||||
|
res, err := api.listAccounts()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("account listing failed", "error", err)
|
||||||
|
return accnts
|
||||||
|
}
|
||||||
|
for _, addr := range res {
|
||||||
|
accnts = append(accnts, accounts.Account{
|
||||||
|
URL: accounts.URL{
|
||||||
|
Scheme: "extapi",
|
||||||
|
Path: api.endpoint,
|
||||||
|
},
|
||||||
|
Address: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
api.cacheMu.Lock()
|
||||||
|
api.cache = accnts
|
||||||
|
api.cacheMu.Unlock()
|
||||||
|
return accnts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
||||||
|
api.cacheMu.RLock()
|
||||||
|
defer api.cacheMu.RUnlock()
|
||||||
|
for _, a := range api.cache {
|
||||||
|
if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||||
|
return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
|
log.Error("operation SelfDerive not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
|
return []byte{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
var res hexutil.Bytes
|
||||||
|
var signAddress = common.NewMixedcaseAddress(account.Address)
|
||||||
|
if err := api.client.Call(&res, "account_signData",
|
||||||
|
mimeType,
|
||||||
|
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
|
||||||
|
hexutil.Encode(data)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If V is on 27/28-form, convert to to 0/1 for Clique
|
||||||
|
if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) {
|
||||||
|
res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
var res hexutil.Bytes
|
||||||
|
var signAddress = common.NewMixedcaseAddress(account.Address)
|
||||||
|
if err := api.client.Call(&res, "account_signData",
|
||||||
|
accounts.MimetypeTextPlain,
|
||||||
|
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
|
||||||
|
hexutil.Encode(text)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
res := ethapi.SignTransactionResult{}
|
||||||
|
to := common.NewMixedcaseAddress(*tx.To())
|
||||||
|
data := hexutil.Bytes(tx.Data())
|
||||||
|
args := &core.SendTxArgs{
|
||||||
|
Data: &data,
|
||||||
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||||
|
Value: hexutil.Big(*tx.Value()),
|
||||||
|
Gas: hexutil.Uint64(tx.Gas()),
|
||||||
|
GasPrice: hexutil.Big(*tx.GasPrice()),
|
||||||
|
To: &to,
|
||||||
|
From: common.NewMixedcaseAddress(account.Address),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
return []byte{}, fmt.Errorf("passphrase-operations not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
return nil, fmt.Errorf("passphrase-operations not supported on external signers")
|
||||||
|
}
|
||||||
|
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("passphrase-operations not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
||||||
|
var res []common.Address
|
||||||
|
if err := api.client.Call(&res, "account_list"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) pingVersion() (string, error) {
|
||||||
|
var v string
|
||||||
|
if err := api.client.Call(&v, "account_version"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
@ -30,8 +30,8 @@ import (
|
||||||
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||||
|
|
||||||
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
// 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
|
// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
|
||||||
// at m/44'/60'/0'/1, etc.
|
// at m/44'/60'/0'/0/1, etc.
|
||||||
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
||||||
|
|
||||||
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"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
|
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
||||||
|
|
@ -79,7 +79,7 @@ func newAccountCache(keydir string) (*accountCache, chan struct{}) {
|
||||||
keydir: keydir,
|
keydir: keydir,
|
||||||
byAddr: make(map[common.Address][]accounts.Account),
|
byAddr: make(map[common.Address][]accounts.Account),
|
||||||
notify: make(chan struct{}, 1),
|
notify: make(chan struct{}, 1),
|
||||||
fileC: fileCache{all: set.NewNonTS()},
|
fileC: fileCache{all: mapset.NewThreadUnsafeSet()},
|
||||||
}
|
}
|
||||||
ac.watcher = newWatcher(ac)
|
ac.watcher = newWatcher(ac)
|
||||||
return ac, ac.notify
|
return ac, ac.notify
|
||||||
|
|
@ -237,7 +237,7 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
log.Debug("Failed to reload keystore contents", "err", err)
|
log.Debug("Failed to reload keystore contents", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
|
if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Create a helper method to scan the contents of the key files
|
// Create a helper method to scan the contents of the key files
|
||||||
|
|
@ -265,22 +265,25 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
case (addr == common.Address{}):
|
case (addr == common.Address{}):
|
||||||
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
|
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
|
||||||
default:
|
default:
|
||||||
return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}
|
return &accounts.Account{
|
||||||
|
Address: addr,
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: path},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Process all the file diffs
|
// Process all the file diffs
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for _, p := range creates.List() {
|
for _, p := range creates.ToSlice() {
|
||||||
if a := readAccount(p.(string)); a != nil {
|
if a := readAccount(p.(string)); a != nil {
|
||||||
ac.add(*a)
|
ac.add(*a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range deletes.List() {
|
for _, p := range deletes.ToSlice() {
|
||||||
ac.deleteByFile(p.(string))
|
ac.deleteByFile(p.(string))
|
||||||
}
|
}
|
||||||
for _, p := range updates.List() {
|
for _, p := range updates.ToSlice() {
|
||||||
path := p.(string)
|
path := p.(string)
|
||||||
ac.deleteByFile(path)
|
ac.deleteByFile(path)
|
||||||
if a := readAccount(path); a != nil {
|
if a := readAccount(path); a != nil {
|
||||||
|
|
|
||||||
|
|
@ -24,20 +24,20 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
set "gopkg.in/fatih/set.v0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileCache is a cache of files seen during scan of keystore.
|
// fileCache is a cache of files seen during scan of keystore.
|
||||||
type fileCache struct {
|
type fileCache struct {
|
||||||
all *set.SetNonTS // Set of all files from the keystore folder
|
all mapset.Set // Set of all files from the keystore folder
|
||||||
lastMod time.Time // Last time instance when a file was modified
|
lastMod time.Time // Last time instance when a file was modified
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan performs a new scan on the given directory, compares against the already
|
// scan performs a new scan on the given directory, compares against the already
|
||||||
// cached filenames, and returns file sets: creates, deletes, updates.
|
// cached filenames, and returns file sets: creates, deletes, updates.
|
||||||
func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
|
func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
|
|
||||||
// List all the failes from the keystore folder
|
// List all the failes from the keystore folder
|
||||||
|
|
@ -51,14 +51,14 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
|
||||||
defer fc.mu.Unlock()
|
defer fc.mu.Unlock()
|
||||||
|
|
||||||
// Iterate all the files and gather their metadata
|
// Iterate all the files and gather their metadata
|
||||||
all := set.NewNonTS()
|
all := mapset.NewThreadUnsafeSet()
|
||||||
mods := set.NewNonTS()
|
mods := mapset.NewThreadUnsafeSet()
|
||||||
|
|
||||||
var newLastMod time.Time
|
var newLastMod time.Time
|
||||||
for _, fi := range files {
|
for _, fi := range files {
|
||||||
// Skip any non-key files from the folder
|
|
||||||
path := filepath.Join(keyDir, fi.Name())
|
path := filepath.Join(keyDir, fi.Name())
|
||||||
if skipKeyFile(fi) {
|
// Skip any non-key files from the folder
|
||||||
|
if nonKeyFile(fi) {
|
||||||
log.Trace("Ignoring file on account scan", "path", path)
|
log.Trace("Ignoring file on account scan", "path", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -76,9 +76,9 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
|
||||||
t2 := time.Now()
|
t2 := time.Now()
|
||||||
|
|
||||||
// Update the tracked files and return the three sets
|
// Update the tracked files and return the three sets
|
||||||
deletes := set.Difference(fc.all, all) // Deletes = previous - current
|
deletes := fc.all.Difference(all) // Deletes = previous - current
|
||||||
creates := set.Difference(all, fc.all) // Creates = current - previous
|
creates := all.Difference(fc.all) // Creates = current - previous
|
||||||
updates := set.Difference(mods, creates) // Updates = modified - creates
|
updates := mods.Difference(creates) // Updates = modified - creates
|
||||||
|
|
||||||
fc.all, fc.lastMod = all, newLastMod
|
fc.all, fc.lastMod = all, newLastMod
|
||||||
t3 := time.Now()
|
t3 := time.Now()
|
||||||
|
|
@ -88,8 +88,8 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
|
||||||
return creates, deletes, updates, nil
|
return creates, deletes, updates, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipKeyFile ignores editor backups, hidden files and folders/symlinks.
|
// nonKeyFile ignores editor backups, hidden files and folders/symlinks.
|
||||||
func skipKeyFile(fi os.FileInfo) bool {
|
func nonKeyFile(fi os.FileInfo) bool {
|
||||||
// Skip editor backups and UNIX-style hidden files.
|
// Skip editor backups and UNIX-style hidden files.
|
||||||
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
if strings.HasSuffix(fi.Name(), "~") || strings.HasPrefix(fi.Name(), ".") {
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
|
|
@ -66,19 +66,19 @@ type plainKeyJSON struct {
|
||||||
|
|
||||||
type encryptedKeyJSONV3 struct {
|
type encryptedKeyJSONV3 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON `json:"crypto"`
|
Crypto CryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedKeyJSONV1 struct {
|
type encryptedKeyJSONV1 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON `json:"crypto"`
|
Crypto CryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptoJSON struct {
|
type CryptoJSON struct {
|
||||||
Cipher string `json:"cipher"`
|
Cipher string `json:"cipher"`
|
||||||
CipherText string `json:"ciphertext"`
|
CipherText string `json:"ciphertext"`
|
||||||
CipherParams cipherparamsJSON `json:"cipherparams"`
|
CipherParams cipherparamsJSON `json:"cipherparams"`
|
||||||
|
|
@ -171,7 +171,10 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, accounts.Account{}, err
|
return nil, accounts.Account{}, err
|
||||||
}
|
}
|
||||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.JoinPath(keyFileName(key.Address))}}
|
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 {
|
if err := ks.StoreKey(a.URL.Path, key, auth); err != nil {
|
||||||
zeroKey(key.PrivateKey)
|
zeroKey(key.PrivateKey)
|
||||||
return nil, a, err
|
return nil, a, err
|
||||||
|
|
@ -179,26 +182,34 @@ func storeNewKey(ks keyStore, rand io.Reader, auth string) (*Key, accounts.Accou
|
||||||
return key, a, err
|
return key, a, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeKeyFile(file string, content []byte) error {
|
func writeTemporaryKeyFile(file string, content []byte) (string, error) {
|
||||||
// Create the keystore directory with appropriate permissions
|
// Create the keystore directory with appropriate permissions
|
||||||
// in case it is not present yet.
|
// in case it is not present yet.
|
||||||
const dirPerm = 0700
|
const dirPerm = 0700
|
||||||
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
|
if err := os.MkdirAll(filepath.Dir(file), dirPerm); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
// Atomic write: create a temporary hidden file first
|
// Atomic write: create a temporary hidden file first
|
||||||
// then move it into place. TempFile assigns mode 0600.
|
// then move it into place. TempFile assigns mode 0600.
|
||||||
f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
|
f, err := ioutil.TempFile(filepath.Dir(file), "."+filepath.Base(file)+".tmp")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
if _, err := f.Write(content); err != nil {
|
if _, err := f.Write(content); err != nil {
|
||||||
f.Close()
|
f.Close()
|
||||||
os.Remove(f.Name())
|
os.Remove(f.Name())
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
return os.Rename(f.Name(), file)
|
return f.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeKeyFile(file string, content []byte) error {
|
||||||
|
name, err := writeTemporaryKeyFile(file, content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(name, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// keyFileName implements the naming convention for keyfiles:
|
// keyFileName implements the naming convention for keyfiles:
|
||||||
|
|
@ -216,5 +227,6 @@ func toISO8601(t time.Time) string {
|
||||||
} else {
|
} else {
|
||||||
tz = fmt.Sprintf("%03d00", offset/3600)
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ var (
|
||||||
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
||||||
|
|
||||||
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
var KeyStoreScheme = "keystore"
|
const KeyStoreScheme = "keystore"
|
||||||
|
|
||||||
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
||||||
const walletRefreshCycle = 3 * time.Second
|
const walletRefreshCycle = 3 * time.Second
|
||||||
|
|
@ -78,7 +78,7 @@ type unlocked struct {
|
||||||
// NewKeyStore creates a keystore for the given directory.
|
// NewKeyStore creates a keystore for the given directory.
|
||||||
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
||||||
keydir, _ = filepath.Abs(keydir)
|
keydir, _ = filepath.Abs(keydir)
|
||||||
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP}}
|
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
|
||||||
ks.init(keydir)
|
ks.init(keydir)
|
||||||
return ks
|
return ks
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,18 +28,19 @@ package keystore
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
crand "crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
|
|
@ -72,6 +73,10 @@ type keyStorePassphrase struct {
|
||||||
keysDirPath string
|
keysDirPath string
|
||||||
scryptN int
|
scryptN int
|
||||||
scryptP int
|
scryptP int
|
||||||
|
// skipKeyFileVerification disables the security-feature which does
|
||||||
|
// reads and decrypts any newly created keyfiles. This should be 'false' in all
|
||||||
|
// cases except tests -- setting this to 'true' is not recommended.
|
||||||
|
skipKeyFileVerification bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string) (*Key, error) {
|
||||||
|
|
@ -93,7 +98,7 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string)
|
||||||
|
|
||||||
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
||||||
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
|
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
|
||||||
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth)
|
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP, false}, rand.Reader, auth)
|
||||||
return a.Address, err
|
return a.Address, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,33 +107,54 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return writeKeyFile(filename, keyjson)
|
// Write into temporary file
|
||||||
|
tmpName, err := writeTemporaryKeyFile(filename, keyjson)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !ks.skipKeyFileVerification {
|
||||||
|
// Verify that we can decrypt the file with the given password.
|
||||||
|
_, err = ks.GetKey(key.Address, tmpName, auth)
|
||||||
|
if err != nil {
|
||||||
|
msg := "An error was encountered when saving and verifying the keystore file. \n" +
|
||||||
|
"This indicates that the keystore is corrupted. \n" +
|
||||||
|
"The corrupted file is stored at \n%v\n" +
|
||||||
|
"Please file a ticket at:\n\n" +
|
||||||
|
"https://github.com/ethereum/go-ethereum/issues." +
|
||||||
|
"The error was : %s"
|
||||||
|
return fmt.Errorf(msg, tmpName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.Rename(tmpName, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||||
if filepath.IsAbs(filename) {
|
if filepath.IsAbs(filename) {
|
||||||
return filename
|
return filename
|
||||||
} else {
|
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
|
||||||
}
|
}
|
||||||
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
// Encryptdata encrypts the data given as 'data' with the password 'auth'.
|
||||||
// blob that can be decrypted later on.
|
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
||||||
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
|
||||||
authArray := []byte(auth)
|
salt := make([]byte, 32)
|
||||||
salt := randentropy.GetEntropyCSPRNG(32)
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
|
derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return CryptoJSON{}, err
|
||||||
}
|
}
|
||||||
encryptKey := derivedKey[:16]
|
encryptKey := derivedKey[:16]
|
||||||
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
|
||||||
|
|
||||||
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
|
iv := make([]byte, aes.BlockSize) // 16
|
||||||
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
|
cipherText, err := aesCTRXOR(encryptKey, data, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return CryptoJSON{}, err
|
||||||
}
|
}
|
||||||
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||||
|
|
||||||
|
|
@ -138,12 +164,11 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
scryptParamsJSON["p"] = scryptP
|
scryptParamsJSON["p"] = scryptP
|
||||||
scryptParamsJSON["dklen"] = scryptDKLen
|
scryptParamsJSON["dklen"] = scryptDKLen
|
||||||
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
||||||
|
|
||||||
cipherParamsJSON := cipherparamsJSON{
|
cipherParamsJSON := cipherparamsJSON{
|
||||||
IV: hex.EncodeToString(iv),
|
IV: hex.EncodeToString(iv),
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoStruct := cryptoJSON{
|
cryptoStruct := CryptoJSON{
|
||||||
Cipher: "aes-128-ctr",
|
Cipher: "aes-128-ctr",
|
||||||
CipherText: hex.EncodeToString(cipherText),
|
CipherText: hex.EncodeToString(cipherText),
|
||||||
CipherParams: cipherParamsJSON,
|
CipherParams: cipherParamsJSON,
|
||||||
|
|
@ -151,6 +176,17 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
KDFParams: scryptParamsJSON,
|
KDFParams: scryptParamsJSON,
|
||||||
MAC: hex.EncodeToString(mac),
|
MAC: hex.EncodeToString(mac),
|
||||||
}
|
}
|
||||||
|
return cryptoStruct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
||||||
|
cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
||||||
hex.EncodeToString(key.Address[:]),
|
hex.EncodeToString(key.Address[:]),
|
||||||
cryptoStruct,
|
cryptoStruct,
|
||||||
|
|
@ -198,42 +234,48 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
||||||
if keyProtected.Version != version {
|
if cryptoJson.Cipher != "aes-128-ctr" {
|
||||||
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
|
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
|
||||||
}
|
}
|
||||||
|
mac, err := hex.DecodeString(cryptoJson.MAC)
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
derivedKey, err := getKDFKey(cryptoJson, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||||
if !bytes.Equal(calculatedMAC, mac) {
|
if !bytes.Equal(calculatedMAC, mac) {
|
||||||
return nil, nil, ErrDecrypt
|
return nil, ErrDecrypt
|
||||||
}
|
}
|
||||||
|
|
||||||
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plainText, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
keyId = uuid.Parse(keyProtected.Id)
|
||||||
|
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +316,7 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt
|
||||||
return plainText, keyId, err
|
return plainText, keyId, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
|
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
|
||||||
authArray := []byte(auth)
|
authArray := []byte(auth)
|
||||||
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -56,7 +56,6 @@ func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
|
||||||
func (ks keyStorePlain) JoinPath(filename string) string {
|
func (ks keyStorePlain) JoinPath(filename string) string {
|
||||||
if filepath.IsAbs(filename) {
|
if filepath.IsAbs(filename) {
|
||||||
return filename
|
return filename
|
||||||
} else {
|
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
|
||||||
}
|
}
|
||||||
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +37,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if encrypted {
|
if encrypted {
|
||||||
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP}
|
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
|
||||||
} else {
|
} else {
|
||||||
ks = &keyStorePlain{d}
|
ks = &keyStorePlain{d}
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +191,7 @@ func TestV1_1(t *testing.T) {
|
||||||
|
|
||||||
func TestV1_2(t *testing.T) {
|
func TestV1_2(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP}
|
ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true}
|
||||||
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
|
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
|
||||||
file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
|
file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
|
||||||
k, err := ks.GetKey(addr, file, "g")
|
k, err := ks.GetKey(addr, file, "g")
|
||||||
|
|
@ -38,7 +38,13 @@ func importPreSaleKey(keyStore keyStore, keyJSON []byte, password string) (accou
|
||||||
return accounts.Account{}, nil, err
|
return accounts.Account{}, nil, err
|
||||||
}
|
}
|
||||||
key.Id = uuid.NewRandom()
|
key.Id = uuid.NewRandom()
|
||||||
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: keyStore.JoinPath(keyFileName(key.Address))}}
|
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)
|
err = keyStore.StoreKey(a.URL.Path, key, password)
|
||||||
return a, key, err
|
return a, key, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
ethereum "github.com/ethereum/go-ethereum"
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// keystoreWallet implements the accounts.Wallet interface for the original
|
// keystoreWallet implements the accounts.Wallet interface for the original
|
||||||
|
|
@ -52,8 +53,8 @@ func (w *keystoreWallet) Status() (string, error) {
|
||||||
// is no connection or decryption step necessary to access the list of accounts.
|
// is no connection or decryption step necessary to access the list of accounts.
|
||||||
func (w *keystoreWallet) Open(passphrase string) error { return nil }
|
func (w *keystoreWallet) Open(passphrase string) error { return nil }
|
||||||
|
|
||||||
// Close implements accounts.Wallet, but is a noop for plain wallets since is no
|
// Close implements accounts.Wallet, but is a noop for plain wallets since there
|
||||||
// meaningful open operation.
|
// is no meaningful open operation.
|
||||||
func (w *keystoreWallet) Close() error { return nil }
|
func (w *keystoreWallet) Close() error { return nil }
|
||||||
|
|
||||||
// Accounts implements accounts.Wallet, returning an account list consisting of
|
// Accounts implements accounts.Wallet, returning an account list consisting of
|
||||||
|
|
@ -78,60 +79,67 @@ func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (account
|
||||||
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
// there is no notion of hierarchical account derivation for plain keystore accounts.
|
||||||
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {}
|
func (w *keystoreWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {}
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, attempting to sign the given hash with
|
// signHash attempts to sign the given hash with
|
||||||
// the given account. If the wallet does not wrap this particular account, an
|
// 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
|
// error is returned to avoid account leakage (even though in theory we may be
|
||||||
// able to sign via our shared keystore backend).
|
// able to sign via our shared keystore backend).
|
||||||
func (w *keystoreWallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
func (w *keystoreWallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if account.Address != w.account.Address {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
return w.keystore.SignHash(account, hash)
|
return w.keystore.SignHash(account, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDataWithPassphrase signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (w *keystoreWallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
if !w.Contains(account) {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// Account seems valid, request the keystore to sign
|
||||||
|
return w.keystore.SignHashWithPassphrase(account, passphrase, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *keystoreWallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTextWithPassphrase implements accounts.Wallet, attempting to sign the
|
||||||
|
// given hash with the given account using passphrase as extra authentication.
|
||||||
|
func (w *keystoreWallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
if !w.Contains(account) {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// Account seems valid, request the keystore to sign
|
||||||
|
return w.keystore.SignHashWithPassphrase(account, passphrase, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
// SignTx implements accounts.Wallet, attempting to sign the given transaction
|
||||||
// with the given account. If the wallet does not wrap this particular account,
|
// 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
|
// an error is returned to avoid account leakage (even though in theory we may
|
||||||
// be able to sign via our shared keystore backend).
|
// 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) {
|
func (w *keystoreWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
// Make sure the requested account is contained within
|
// Make sure the requested account is contained within
|
||||||
if account.Address != w.account.Address {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
return w.keystore.SignTx(account, tx, chainID)
|
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
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
// transaction with the given account using passphrase as extra authentication.
|
// 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) {
|
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
|
// Make sure the requested account is contained within
|
||||||
if account.Address != w.account.Address {
|
if !w.Contains(account) {
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
return nil, accounts.ErrUnknownAccount
|
||||||
}
|
}
|
||||||
// Account seems valid, request the keystore to sign
|
// Account seems valid, request the keystore to sign
|
||||||
|
|
@ -74,6 +74,22 @@ func (u URL) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(u.String())
|
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:
|
// Cmp compares x and y and returns:
|
||||||
//
|
//
|
||||||
// -1 if x < y
|
// -1 if x < y
|
||||||
|
|
|
||||||
96
accounts/url_test.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
// 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 (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLParsing(t *testing.T) {
|
||||||
|
url, err := parseURL("https://ethereum.org")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = parseURL("ethereum.org")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected err, got: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLString(t *testing.T) {
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
if url.String() != "https://ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
url = URL{Scheme: "", Path: "ethereum.org"}
|
||||||
|
if url.String() != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLMarshalJSON(t *testing.T) {
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
json, err := url.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpcted error: %v", err)
|
||||||
|
}
|
||||||
|
if string(json) != "\"https://ethereum.org\"" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLUnmarshalJSON(t *testing.T) {
|
||||||
|
url := &URL{}
|
||||||
|
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpcted error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLComparison(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
urlA URL
|
||||||
|
urlB URL
|
||||||
|
expect int
|
||||||
|
}{
|
||||||
|
{URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0},
|
||||||
|
{URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
{URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1},
|
||||||
|
{URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
result := tt.urlA.Cmp(tt.urlB)
|
||||||
|
if result != tt.expect {
|
||||||
|
t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ func (hub *Hub) refreshWallets() {
|
||||||
// breaking the Ledger protocol if that is waiting for user confirmation. This
|
// 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
|
// 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
|
// need to prevent concurrent comms ourselves. The more elegant solution would
|
||||||
// be to ditch enumeration in favor of hutplug events, but that don't work yet
|
// 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.
|
// on Windows so if we need to hack it anyway, this is more elegant for now.
|
||||||
hub.commsLock.Lock()
|
hub.commsLock.Lock()
|
||||||
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,9 @@ const (
|
||||||
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
||||||
|
|
||||||
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
||||||
ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
||||||
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent 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
|
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
||||||
ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
||||||
|
|
@ -259,7 +257,9 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
|
||||||
|
|
||||||
// Decode the hex sting into an Ethereum address and return
|
// Decode the hex sting into an Ethereum address and return
|
||||||
var address common.Address
|
var address common.Address
|
||||||
hex.Decode(address[:], hexstr)
|
if _, err = hex.Decode(address[:], hexstr); err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
return address, nil
|
return address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,7 +304,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
for i, component := range derivationPath {
|
for i, component := range derivationPath {
|
||||||
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
||||||
}
|
}
|
||||||
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste
|
// Create the transaction RLP based on whether legacy or EIP155 signing was requested
|
||||||
var (
|
var (
|
||||||
txrlp []byte
|
txrlp []byte
|
||||||
err error
|
err error
|
||||||
|
|
@ -352,7 +352,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
signer = new(types.HomesteadSigner)
|
signer = new(types.HomesteadSigner)
|
||||||
} else {
|
} else {
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.NewEIP155Signer(chainID)
|
||||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
}
|
}
|
||||||
signed, err := tx.WithSignature(signer, signature)
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor"
|
"github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -41,6 +41,9 @@ import (
|
||||||
// encoded passphrase.
|
// encoded passphrase.
|
||||||
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
||||||
|
|
||||||
|
// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
|
||||||
|
var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
|
||||||
|
|
||||||
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
||||||
// if the device replies with a mismatching header. This usually means the device
|
// if the device replies with a mismatching header. This usually means the device
|
||||||
// is in browser mode.
|
// is in browser mode.
|
||||||
|
|
@ -52,6 +55,7 @@ type trezorDriver struct {
|
||||||
version [3]uint32 // Current version of the Trezor firmware
|
version [3]uint32 // Current version of the Trezor firmware
|
||||||
label string // Current textual label of the Trezor device
|
label string // Current textual label of the Trezor device
|
||||||
pinwait bool // Flags whether the device is waiting for PIN entry
|
pinwait bool // Flags whether the device is waiting for PIN entry
|
||||||
|
passphrasewait bool // Flags whether the device is waiting for passphrase entry
|
||||||
failure error // Any failure that would make the device unusable
|
failure error // Any failure that would make the device unusable
|
||||||
log log.Logger // Contextual logger to tag the trezor with its id
|
log log.Logger // Contextual logger to tag the trezor with its id
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +83,7 @@ func (w *trezorDriver) Status() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open implements usbwallet.driver, attempting to initialize the connection to
|
// Open implements usbwallet.driver, attempting to initialize the connection to
|
||||||
// the Trezor hardware wallet. Initializing the Trezor is a two phase operation:
|
// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
|
||||||
// * The first phase is to initialize the connection and read the wallet's
|
// * 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
|
// 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
|
// device will display the pinpad as a result and will return an appropriate
|
||||||
|
|
@ -87,11 +91,13 @@ func (w *trezorDriver) Status() (string, error) {
|
||||||
// * The second phase is to unlock access to the Trezor, which is done by the
|
// * 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
|
// user actually providing a passphrase mapping a keyboard keypad to the pin
|
||||||
// number of the user (shuffled according to the pinpad displayed).
|
// number of the user (shuffled according to the pinpad displayed).
|
||||||
|
// * If needed the device will ask for passphrase which will require calling
|
||||||
|
// open again with the actual passphrase (3rd phase)
|
||||||
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
w.device, w.failure = device, nil
|
w.device, w.failure = device, nil
|
||||||
|
|
||||||
// If phase 1 is requested, init the connection and wait for user callback
|
// If phase 1 is requested, init the connection and wait for user callback
|
||||||
if passphrase == "" {
|
if passphrase == "" && !w.passphrasewait {
|
||||||
// If we're already waiting for a PIN entry, insta-return
|
// If we're already waiting for a PIN entry, insta-return
|
||||||
if w.pinwait {
|
if w.pinwait {
|
||||||
return ErrTrezorPINNeeded
|
return ErrTrezorPINNeeded
|
||||||
|
|
@ -104,26 +110,46 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
||||||
w.label = features.GetLabel()
|
w.label = features.GetLabel()
|
||||||
|
|
||||||
// Do a manual ping, forcing the device to ask for its PIN
|
// Do a manual ping, forcing the device to ask for its PIN and Passphrase
|
||||||
askPin := true
|
askPin := true
|
||||||
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success))
|
askPassphrase := true
|
||||||
|
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Only return the PIN request if the device wasn't unlocked until now
|
// Only return the PIN request if the device wasn't unlocked until now
|
||||||
if res == 1 {
|
switch res {
|
||||||
return nil // Device responded with trezor.Success
|
case 0:
|
||||||
}
|
|
||||||
w.pinwait = true
|
w.pinwait = true
|
||||||
return ErrTrezorPINNeeded
|
return ErrTrezorPINNeeded
|
||||||
|
case 1:
|
||||||
|
w.pinwait = false
|
||||||
|
w.passphrasewait = true
|
||||||
|
return ErrTrezorPassphraseNeeded
|
||||||
|
case 2:
|
||||||
|
return nil // responded with trezor.Success
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Phase 2 requested with actual PIN entry
|
// Phase 2 requested with actual PIN entry
|
||||||
|
if w.pinwait {
|
||||||
w.pinwait = false
|
w.pinwait = false
|
||||||
|
res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
|
||||||
if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil {
|
if err != nil {
|
||||||
w.failure = err
|
w.failure = err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if res == 1 {
|
||||||
|
w.passphrasewait = true
|
||||||
|
return ErrTrezorPassphraseNeeded
|
||||||
|
}
|
||||||
|
} else if w.passphrasewait {
|
||||||
|
w.passphrasewait = false
|
||||||
|
if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,7 +247,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||||
signer = new(types.HomesteadSigner)
|
signer = new(types.HomesteadSigner)
|
||||||
} else {
|
} else {
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.NewEIP155Signer(chainID)
|
||||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
}
|
}
|
||||||
// Inject the final signature into the transaction and sanity check the sender
|
// Inject the final signature into the transaction and sanity check the sender
|
||||||
signed, err := tx.WithSignature(signer, signature)
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func Type(msg proto.Message) uint16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the friendly message type name of a specific protocol buffer
|
// Name returns the friendly message type name of a specific protocol buffer
|
||||||
// type numbers.
|
// type number.
|
||||||
func Name(kind uint16) string {
|
func Name(kind uint16) string {
|
||||||
name := MessageType_name[int32(kind)]
|
name := MessageType_name[int32(kind)]
|
||||||
if len(name) < 12 {
|
if len(name) < 12 {
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/hid"
|
"github.com/karalabe/hid"
|
||||||
)
|
)
|
||||||
|
|
@ -99,7 +100,7 @@ type wallet struct {
|
||||||
//
|
//
|
||||||
// As such, a hardware wallet needs two locks to function correctly. A state
|
// 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
|
// lock can be used to protect the wallet's software-side internal state, which
|
||||||
// must not be held exlusively during hardware communication. A communication
|
// must not be held exclusively during hardware communication. A communication
|
||||||
// lock can be used to achieve exclusive access to the device itself, this one
|
// 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
|
// however should allow "skipping" waiting for operations that might want to
|
||||||
// use the device, but can live without too (e.g. account self-derivation).
|
// use the device, but can live without too (e.g. account self-derivation).
|
||||||
|
|
@ -495,12 +496,28 @@ func (w *wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainSt
|
||||||
w.deriveChain = chain
|
w.deriveChain = chain
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
// signHash implements accounts.Wallet, however signing arbitrary data is not
|
||||||
// supported for hardware wallets, so this method will always return an error.
|
// supported for hardware wallets, so this method will always return an error.
|
||||||
func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
return nil, accounts.ErrNotSupported
|
return nil, accounts.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, crypto.Keccak256(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
// data with the given account using passphrase as extra authentication.
|
||||||
|
// Since USB wallets don't rely on passphrases, these are silently ignored.
|
||||||
|
func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return w.SignData(account, mimeType, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
return w.signHash(account, accounts.TextHash(text))
|
||||||
|
}
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
// 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
|
// wallet to request a confirmation from the user. It returns either the signed
|
||||||
// transaction or a failure if the user denied the transaction.
|
// transaction or a failure if the user denied the transaction.
|
||||||
|
|
@ -550,8 +567,8 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||||
// data is not supported for Ledger wallets, so this method will always return
|
// data is not supported for Ledger wallets, so this method will always return
|
||||||
// an error.
|
// an error.
|
||||||
func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
return w.SignHash(account, hash)
|
return w.SignText(account, accounts.TextHash(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.2.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://dl.google.com/go/go1.12.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.9.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.12.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
|
||||||
561
bmt/bmt.go
|
|
@ -1,561 +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 bmt provides a binary merkle tree implementation
|
|
||||||
package bmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Binary Merkle Tree Hash is a hash function over arbitrary datachunks of limited size
|
|
||||||
It is defined as the root hash of the binary merkle tree built over fixed size segments
|
|
||||||
of the underlying chunk using any base hash function (e.g keccak 256 SHA3)
|
|
||||||
|
|
||||||
It is used as the chunk hash function in swarm which in turn is the basis for the
|
|
||||||
128 branching swarm hash http://swarm-guide.readthedocs.io/en/latest/architecture.html#swarm-hash
|
|
||||||
|
|
||||||
The BMT is optimal for providing compact inclusion proofs, i.e. prove that a
|
|
||||||
segment is a substring of a chunk starting at a particular offset
|
|
||||||
The size of the underlying segments is fixed at 32 bytes (called the resolution
|
|
||||||
of the BMT hash), the EVM word size to optimize for on-chain BMT verification
|
|
||||||
as well as the hash size optimal for inclusion proofs in the merkle tree of the swarm hash.
|
|
||||||
|
|
||||||
Two implementations are provided:
|
|
||||||
|
|
||||||
* RefHasher is optimized for code simplicity and meant as a reference implementation
|
|
||||||
* Hasher is optimized for speed taking advantage of concurrency with minimalistic
|
|
||||||
control structure to coordinate the concurrent routines
|
|
||||||
It implements the ChunkHash interface as well as the go standard hash.Hash interface
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultSegmentCount is the maximum number of segments of the underlying chunk
|
|
||||||
DefaultSegmentCount = 128 // Should be equal to storage.DefaultBranches
|
|
||||||
// DefaultPoolSize is the maximum number of bmt trees used by the hashers, i.e,
|
|
||||||
// the maximum number of concurrent BMT hashing operations performed by the same hasher
|
|
||||||
DefaultPoolSize = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// BaseHasher is a hash.Hash constructor function used for the base hash of the BMT.
|
|
||||||
type BaseHasher func() hash.Hash
|
|
||||||
|
|
||||||
// Hasher a reusable hasher for fixed maximum size chunks representing a BMT
|
|
||||||
// implements the hash.Hash interface
|
|
||||||
// reuse pool of Tree-s for amortised memory allocation and resource control
|
|
||||||
// supports order-agnostic concurrent segment writes
|
|
||||||
// as well as sequential read and write
|
|
||||||
// can not be called concurrently on more than one chunk
|
|
||||||
// can be further appended after Sum
|
|
||||||
// Reset gives back the Tree to the pool and guaranteed to leave
|
|
||||||
// the tree and itself in a state reusable for hashing a new chunk
|
|
||||||
type Hasher struct {
|
|
||||||
pool *TreePool // BMT resource pool
|
|
||||||
bmt *Tree // prebuilt BMT resource for flowcontrol and proofs
|
|
||||||
blocksize int // segment size (size of hash) also for hash.Hash
|
|
||||||
count int // segment count
|
|
||||||
size int // for hash.Hash same as hashsize
|
|
||||||
cur int // cursor position for righmost currently open chunk
|
|
||||||
segment []byte // the rightmost open segment (not complete)
|
|
||||||
depth int // index of last level
|
|
||||||
result chan []byte // result channel
|
|
||||||
hash []byte // to record the result
|
|
||||||
max int32 // max segments for SegmentWriter interface
|
|
||||||
blockLength []byte // The block length that needes to be added in Sum
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a reusable Hasher
|
|
||||||
// implements the hash.Hash interface
|
|
||||||
// pulls a new Tree from a resource pool for hashing each chunk
|
|
||||||
func New(p *TreePool) *Hasher {
|
|
||||||
return &Hasher{
|
|
||||||
pool: p,
|
|
||||||
depth: depth(p.SegmentCount),
|
|
||||||
size: p.SegmentSize,
|
|
||||||
blocksize: p.SegmentSize,
|
|
||||||
count: p.SegmentCount,
|
|
||||||
result: make(chan []byte),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is a reuseable segment hasher representing a node in a BMT
|
|
||||||
// it allows for continued writes after a Sum
|
|
||||||
// and is left in completely reusable state after Reset
|
|
||||||
type Node struct {
|
|
||||||
level, index int // position of node for information/logging only
|
|
||||||
initial bool // first and last node
|
|
||||||
root bool // whether the node is root to a smaller BMT
|
|
||||||
isLeft bool // whether it is left side of the parent double segment
|
|
||||||
unbalanced bool // indicates if a node has only the left segment
|
|
||||||
parent *Node // BMT connections
|
|
||||||
state int32 // atomic increment impl concurrent boolean toggle
|
|
||||||
left, right []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNode constructor for segment hasher nodes in the BMT
|
|
||||||
func NewNode(level, index int, parent *Node) *Node {
|
|
||||||
return &Node{
|
|
||||||
parent: parent,
|
|
||||||
level: level,
|
|
||||||
index: index,
|
|
||||||
initial: index == 0,
|
|
||||||
isLeft: index%2 == 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TreePool provides a pool of Trees used as resources by Hasher
|
|
||||||
// a Tree popped from the pool is guaranteed to have clean state
|
|
||||||
// for hashing a new chunk
|
|
||||||
// Hasher Reset releases the Tree to the pool
|
|
||||||
type TreePool struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
c chan *Tree
|
|
||||||
hasher BaseHasher
|
|
||||||
SegmentSize int
|
|
||||||
SegmentCount int
|
|
||||||
Capacity int
|
|
||||||
count int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTreePool creates a Tree pool with hasher, segment size, segment count and capacity
|
|
||||||
// on GetTree it reuses free Trees or creates a new one if size is not reached
|
|
||||||
func NewTreePool(hasher BaseHasher, segmentCount, capacity int) *TreePool {
|
|
||||||
return &TreePool{
|
|
||||||
c: make(chan *Tree, capacity),
|
|
||||||
hasher: hasher,
|
|
||||||
SegmentSize: hasher().Size(),
|
|
||||||
SegmentCount: segmentCount,
|
|
||||||
Capacity: capacity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drain drains the pool uptil it has no more than n resources
|
|
||||||
func (self *TreePool) Drain(n int) {
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
for len(self.c) > n {
|
|
||||||
<-self.c
|
|
||||||
self.count--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserve is blocking until it returns an available Tree
|
|
||||||
// it reuses free Trees or creates a new one if size is not reached
|
|
||||||
func (self *TreePool) Reserve() *Tree {
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
var t *Tree
|
|
||||||
if self.count == self.Capacity {
|
|
||||||
return <-self.c
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case t = <-self.c:
|
|
||||||
default:
|
|
||||||
t = NewTree(self.hasher, self.SegmentSize, self.SegmentCount)
|
|
||||||
self.count++
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release gives back a Tree to the pool.
|
|
||||||
// This Tree is guaranteed to be in reusable state
|
|
||||||
// does not need locking
|
|
||||||
func (self *TreePool) Release(t *Tree) {
|
|
||||||
self.c <- t // can never fail but...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree is a reusable control structure representing a BMT
|
|
||||||
// organised in a binary tree
|
|
||||||
// Hasher uses a TreePool to pick one for each chunk hash
|
|
||||||
// the Tree is 'locked' while not in the pool
|
|
||||||
type Tree struct {
|
|
||||||
leaves []*Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw draws the BMT (badly)
|
|
||||||
func (self *Tree) Draw(hash []byte, d int) string {
|
|
||||||
var left, right []string
|
|
||||||
var anc []*Node
|
|
||||||
for i, n := range self.leaves {
|
|
||||||
left = append(left, fmt.Sprintf("%v", hashstr(n.left)))
|
|
||||||
if i%2 == 0 {
|
|
||||||
anc = append(anc, n.parent)
|
|
||||||
}
|
|
||||||
right = append(right, fmt.Sprintf("%v", hashstr(n.right)))
|
|
||||||
}
|
|
||||||
anc = self.leaves
|
|
||||||
var hashes [][]string
|
|
||||||
for l := 0; len(anc) > 0; l++ {
|
|
||||||
var nodes []*Node
|
|
||||||
hash := []string{""}
|
|
||||||
for i, n := range anc {
|
|
||||||
hash = append(hash, fmt.Sprintf("%v|%v", hashstr(n.left), hashstr(n.right)))
|
|
||||||
if i%2 == 0 && n.parent != nil {
|
|
||||||
nodes = append(nodes, n.parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash = append(hash, "")
|
|
||||||
hashes = append(hashes, hash)
|
|
||||||
anc = nodes
|
|
||||||
}
|
|
||||||
hashes = append(hashes, []string{"", fmt.Sprintf("%v", hashstr(hash)), ""})
|
|
||||||
total := 60
|
|
||||||
del := " "
|
|
||||||
var rows []string
|
|
||||||
for i := len(hashes) - 1; i >= 0; i-- {
|
|
||||||
var textlen int
|
|
||||||
hash := hashes[i]
|
|
||||||
for _, s := range hash {
|
|
||||||
textlen += len(s)
|
|
||||||
}
|
|
||||||
if total < textlen {
|
|
||||||
total = textlen + len(hash)
|
|
||||||
}
|
|
||||||
delsize := (total - textlen) / (len(hash) - 1)
|
|
||||||
if delsize > len(del) {
|
|
||||||
delsize = len(del)
|
|
||||||
}
|
|
||||||
row := fmt.Sprintf("%v: %v", len(hashes)-i-1, strings.Join(hash, del[:delsize]))
|
|
||||||
rows = append(rows, row)
|
|
||||||
|
|
||||||
}
|
|
||||||
rows = append(rows, strings.Join(left, " "))
|
|
||||||
rows = append(rows, strings.Join(right, " "))
|
|
||||||
return strings.Join(rows, "\n") + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTree initialises the Tree by building up the nodes of a BMT
|
|
||||||
// segment size is stipulated to be the size of the hash
|
|
||||||
// segmentCount needs to be positive integer and does not need to be
|
|
||||||
// a power of two and can even be an odd number
|
|
||||||
// segmentSize * segmentCount determines the maximum chunk size
|
|
||||||
// hashed using the tree
|
|
||||||
func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree {
|
|
||||||
n := NewNode(0, 0, nil)
|
|
||||||
n.root = true
|
|
||||||
prevlevel := []*Node{n}
|
|
||||||
// iterate over levels and creates 2^level nodes
|
|
||||||
level := 1
|
|
||||||
count := 2
|
|
||||||
for d := 1; d <= depth(segmentCount); d++ {
|
|
||||||
nodes := make([]*Node, count)
|
|
||||||
for i := 0; i < len(nodes); i++ {
|
|
||||||
parent := prevlevel[i/2]
|
|
||||||
t := NewNode(level, i, parent)
|
|
||||||
nodes[i] = t
|
|
||||||
}
|
|
||||||
prevlevel = nodes
|
|
||||||
level++
|
|
||||||
count *= 2
|
|
||||||
}
|
|
||||||
// the datanode level is the nodes on the last level where
|
|
||||||
return &Tree{
|
|
||||||
leaves: prevlevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods needed by hash.Hash
|
|
||||||
|
|
||||||
// Size returns the size
|
|
||||||
func (self *Hasher) Size() int {
|
|
||||||
return self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockSize returns the block size
|
|
||||||
func (self *Hasher) BlockSize() int {
|
|
||||||
return self.blocksize
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum returns the hash of the buffer
|
|
||||||
// hash.Hash interface Sum method appends the byte slice to the underlying
|
|
||||||
// data before it calculates and returns the hash of the chunk
|
|
||||||
func (self *Hasher) Sum(b []byte) (r []byte) {
|
|
||||||
t := self.bmt
|
|
||||||
i := self.cur
|
|
||||||
n := t.leaves[i]
|
|
||||||
j := i
|
|
||||||
// must run strictly before all nodes calculate
|
|
||||||
// datanodes are guaranteed to have a parent
|
|
||||||
if len(self.segment) > self.size && i > 0 && n.parent != nil {
|
|
||||||
n = n.parent
|
|
||||||
} else {
|
|
||||||
i *= 2
|
|
||||||
}
|
|
||||||
d := self.finalise(n, i)
|
|
||||||
self.writeSegment(j, self.segment, d)
|
|
||||||
c := <-self.result
|
|
||||||
self.releaseTree()
|
|
||||||
|
|
||||||
// sha3(length + BMT(pure_chunk))
|
|
||||||
if self.blockLength == nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
res := self.pool.hasher()
|
|
||||||
res.Reset()
|
|
||||||
res.Write(self.blockLength)
|
|
||||||
res.Write(c)
|
|
||||||
return res.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the SwarmHash interface
|
|
||||||
|
|
||||||
// Hash waits for the hasher result and returns it
|
|
||||||
// caller must call this on a BMT Hasher being written to
|
|
||||||
func (self *Hasher) Hash() []byte {
|
|
||||||
return <-self.result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the io.Writer interface
|
|
||||||
|
|
||||||
// Write fills the buffer to hash
|
|
||||||
// with every full segment complete launches a hasher go routine
|
|
||||||
// that shoots up the BMT
|
|
||||||
func (self *Hasher) Write(b []byte) (int, error) {
|
|
||||||
l := len(b)
|
|
||||||
if l <= 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
s := self.segment
|
|
||||||
i := self.cur
|
|
||||||
count := (self.count + 1) / 2
|
|
||||||
need := self.count*self.size - self.cur*2*self.size
|
|
||||||
size := self.size
|
|
||||||
if need > size {
|
|
||||||
size *= 2
|
|
||||||
}
|
|
||||||
if l < need {
|
|
||||||
need = l
|
|
||||||
}
|
|
||||||
// calculate missing bit to complete current open segment
|
|
||||||
rest := size - len(s)
|
|
||||||
if need < rest {
|
|
||||||
rest = need
|
|
||||||
}
|
|
||||||
s = append(s, b[:rest]...)
|
|
||||||
need -= rest
|
|
||||||
// read full segments and the last possibly partial segment
|
|
||||||
for need > 0 && i < count-1 {
|
|
||||||
// push all finished chunks we read
|
|
||||||
self.writeSegment(i, s, self.depth)
|
|
||||||
need -= size
|
|
||||||
if need < 0 {
|
|
||||||
size += need
|
|
||||||
}
|
|
||||||
s = b[rest : rest+size]
|
|
||||||
rest += size
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
self.segment = s
|
|
||||||
self.cur = i
|
|
||||||
// otherwise, we can assume len(s) == 0, so all buffer is read and chunk is not yet full
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the io.ReaderFrom interface
|
|
||||||
|
|
||||||
// ReadFrom reads from io.Reader and appends to the data to hash using Write
|
|
||||||
// it reads so that chunk to hash is maximum length or reader reaches EOF
|
|
||||||
// caller must Reset the hasher prior to call
|
|
||||||
func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) {
|
|
||||||
bufsize := self.size*self.count - self.size*self.cur - len(self.segment)
|
|
||||||
buf := make([]byte, bufsize)
|
|
||||||
var read int
|
|
||||||
for {
|
|
||||||
var n int
|
|
||||||
n, err = r.Read(buf)
|
|
||||||
read += n
|
|
||||||
if err == io.EOF || read == len(buf) {
|
|
||||||
hash := self.Sum(buf[:n])
|
|
||||||
if read == len(buf) {
|
|
||||||
err = NewEOC(hash)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
n, err = self.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return int64(read), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset needs to be called before writing to the hasher
|
|
||||||
func (self *Hasher) Reset() {
|
|
||||||
self.getTree()
|
|
||||||
self.blockLength = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the SwarmHash interface
|
|
||||||
|
|
||||||
// ResetWithLength needs to be called before writing to the hasher
|
|
||||||
// the argument is supposed to be the byte slice binary representation of
|
|
||||||
// the legth of the data subsumed under the hash
|
|
||||||
func (self *Hasher) ResetWithLength(l []byte) {
|
|
||||||
self.Reset()
|
|
||||||
self.blockLength = l
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release gives back the Tree to the pool whereby it unlocks
|
|
||||||
// it resets tree, segment and index
|
|
||||||
func (self *Hasher) releaseTree() {
|
|
||||||
if self.bmt != nil {
|
|
||||||
n := self.bmt.leaves[self.cur]
|
|
||||||
for ; n != nil; n = n.parent {
|
|
||||||
n.unbalanced = false
|
|
||||||
if n.parent != nil {
|
|
||||||
n.root = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.pool.Release(self.bmt)
|
|
||||||
self.bmt = nil
|
|
||||||
|
|
||||||
}
|
|
||||||
self.cur = 0
|
|
||||||
self.segment = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Hasher) writeSegment(i int, s []byte, d int) {
|
|
||||||
h := self.pool.hasher()
|
|
||||||
n := self.bmt.leaves[i]
|
|
||||||
|
|
||||||
if len(s) > self.size && n.parent != nil {
|
|
||||||
go func() {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(s)
|
|
||||||
s = h.Sum(nil)
|
|
||||||
|
|
||||||
if n.root {
|
|
||||||
self.result <- s
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.run(n.parent, h, d, n.index, s)
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go self.run(n, h, d, i*2, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Hasher) run(n *Node, h hash.Hash, d int, i int, s []byte) {
|
|
||||||
isLeft := i%2 == 0
|
|
||||||
for {
|
|
||||||
if isLeft {
|
|
||||||
n.left = s
|
|
||||||
} else {
|
|
||||||
n.right = s
|
|
||||||
}
|
|
||||||
if !n.unbalanced && n.toggle() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !n.unbalanced || !isLeft || i == 0 && d == 0 {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(n.left)
|
|
||||||
h.Write(n.right)
|
|
||||||
s = h.Sum(nil)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
s = append(n.left, n.right...)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hash = s
|
|
||||||
if n.root {
|
|
||||||
self.result <- s
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isLeft = n.isLeft
|
|
||||||
n = n.parent
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTree obtains a BMT resource by reserving one from the pool
|
|
||||||
func (self *Hasher) getTree() *Tree {
|
|
||||||
if self.bmt != nil {
|
|
||||||
return self.bmt
|
|
||||||
}
|
|
||||||
t := self.pool.Reserve()
|
|
||||||
self.bmt = t
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// atomic bool toggle implementing a concurrent reusable 2-state object
|
|
||||||
// atomic addint with %2 implements atomic bool toggle
|
|
||||||
// it returns true if the toggler just put it in the active/waiting state
|
|
||||||
func (self *Node) toggle() bool {
|
|
||||||
return atomic.AddInt32(&self.state, 1)%2 == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashstr(b []byte) string {
|
|
||||||
end := len(b)
|
|
||||||
if end > 4 {
|
|
||||||
end = 4
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%x", b[:end])
|
|
||||||
}
|
|
||||||
|
|
||||||
func depth(n int) (d int) {
|
|
||||||
for l := (n - 1) / 2; l > 0; l /= 2 {
|
|
||||||
d++
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// finalise is following the zigzags on the tree belonging
|
|
||||||
// to the final datasegment
|
|
||||||
func (self *Hasher) finalise(n *Node, i int) (d int) {
|
|
||||||
isLeft := i%2 == 0
|
|
||||||
for {
|
|
||||||
// when the final segment's path is going via left segments
|
|
||||||
// the incoming data is pushed to the parent upon pulling the left
|
|
||||||
// we do not need toogle the state since this condition is
|
|
||||||
// detectable
|
|
||||||
n.unbalanced = isLeft
|
|
||||||
n.right = nil
|
|
||||||
if n.initial {
|
|
||||||
n.root = true
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
isLeft = n.isLeft
|
|
||||||
n = n.parent
|
|
||||||
d++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOC (end of chunk) implements the error interface
|
|
||||||
type EOC struct {
|
|
||||||
Hash []byte // read the hash of the chunk off the error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the error string
|
|
||||||
func (self *EOC) Error() string {
|
|
||||||
return fmt.Sprintf("hasher limit reached, chunk hash: %x", self.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEOC creates new end of chunk error with the hash
|
|
||||||
func NewEOC(hash []byte) *EOC {
|
|
||||||
return &EOC{hash}
|
|
||||||
}
|
|
||||||
85
bmt/bmt_r.go
|
|
@ -1,85 +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/>.
|
|
||||||
|
|
||||||
// simple nonconcurrent reference implementation for hashsize segment based
|
|
||||||
// Binary Merkle tree hash on arbitrary but fixed maximum chunksize
|
|
||||||
//
|
|
||||||
// This implementation does not take advantage of any paralellisms and uses
|
|
||||||
// far more memory than necessary, but it is easy to see that it is correct.
|
|
||||||
// It can be used for generating test cases for optimized implementations.
|
|
||||||
// see testBMTHasherCorrectness function in bmt_test.go
|
|
||||||
package bmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RefHasher is the non-optimized easy to read reference implementation of BMT
|
|
||||||
type RefHasher struct {
|
|
||||||
span int
|
|
||||||
section int
|
|
||||||
cap int
|
|
||||||
h hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRefHasher returns a new RefHasher
|
|
||||||
func NewRefHasher(hasher BaseHasher, count int) *RefHasher {
|
|
||||||
h := hasher()
|
|
||||||
hashsize := h.Size()
|
|
||||||
maxsize := hashsize * count
|
|
||||||
c := 2
|
|
||||||
for ; c < count; c *= 2 {
|
|
||||||
}
|
|
||||||
if c > 2 {
|
|
||||||
c /= 2
|
|
||||||
}
|
|
||||||
return &RefHasher{
|
|
||||||
section: 2 * hashsize,
|
|
||||||
span: c * hashsize,
|
|
||||||
cap: maxsize,
|
|
||||||
h: h,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns the BMT hash of the byte slice
|
|
||||||
// implements the SwarmHash interface
|
|
||||||
func (rh *RefHasher) Hash(d []byte) []byte {
|
|
||||||
if len(d) > rh.cap {
|
|
||||||
d = d[:rh.cap]
|
|
||||||
}
|
|
||||||
|
|
||||||
return rh.hash(d, rh.span)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rh *RefHasher) hash(d []byte, s int) []byte {
|
|
||||||
l := len(d)
|
|
||||||
left := d
|
|
||||||
var right []byte
|
|
||||||
if l > rh.section {
|
|
||||||
for ; s >= l; s /= 2 {
|
|
||||||
}
|
|
||||||
left = rh.hash(d[:s], s)
|
|
||||||
right = d[s:]
|
|
||||||
if l-s > rh.section/2 {
|
|
||||||
right = rh.hash(right, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer rh.h.Reset()
|
|
||||||
rh.h.Write(left)
|
|
||||||
rh.h.Write(right)
|
|
||||||
h := rh.h.Sum(nil)
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
481
bmt/bmt_test.go
|
|
@ -1,481 +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 bmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
crand "crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxproccnt = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRefHasher tests that the RefHasher computes the expected BMT hash for
|
|
||||||
// all data lengths between 0 and 256 bytes
|
|
||||||
func TestRefHasher(t *testing.T) {
|
|
||||||
hashFunc := sha3.NewKeccak256
|
|
||||||
|
|
||||||
sha3 := func(data ...[]byte) []byte {
|
|
||||||
h := hashFunc()
|
|
||||||
for _, v := range data {
|
|
||||||
h.Write(v)
|
|
||||||
}
|
|
||||||
return h.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the test struct is used to specify the expected BMT hash for data
|
|
||||||
// lengths between "from" and "to"
|
|
||||||
type test struct {
|
|
||||||
from int64
|
|
||||||
to int64
|
|
||||||
expected func([]byte) []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests []*test
|
|
||||||
|
|
||||||
// all lengths in [0,64] should be:
|
|
||||||
//
|
|
||||||
// sha3(data)
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 0,
|
|
||||||
to: 64,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(data)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [65,96] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// data[64:]
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 65,
|
|
||||||
to: 96,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(data[:64]), data[64:])
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [97,128] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:])
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 97,
|
|
||||||
to: 128,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(data[:64]), sha3(data[64:]))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [129,160] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// data[128:]
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 129,
|
|
||||||
to: 160,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), data[128:])
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [161,192] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// sha3(data[128:])
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 161,
|
|
||||||
to: 192,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(data[128:]))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [193,224] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[128:192])
|
|
||||||
// data[192:]
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 193,
|
|
||||||
to: 224,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), data[192:]))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [225,256] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[128:192])
|
|
||||||
// sha3(data[192:])
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 225,
|
|
||||||
to: 256,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), sha3(data[192:])))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// run the tests
|
|
||||||
for _, x := range tests {
|
|
||||||
for length := x.from; length <= x.to; length++ {
|
|
||||||
t.Run(fmt.Sprintf("%d_bytes", length), func(t *testing.T) {
|
|
||||||
data := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(crand.Reader, data); err != nil && err != io.EOF {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expected := x.expected(data)
|
|
||||||
actual := NewRefHasher(hashFunc, 128).Hash(data)
|
|
||||||
if !bytes.Equal(actual, expected) {
|
|
||||||
t.Fatalf("expected %x, got %x", expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDataReader(l int) (r io.Reader) {
|
|
||||||
return io.LimitReader(crand.Reader, int64(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherCorrectness(t *testing.T) {
|
|
||||||
err := testHasher(testBaseHasher)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHasher(f func(BaseHasher, []byte, int, int) error) error {
|
|
||||||
tdata := testDataReader(4128)
|
|
||||||
data := make([]byte, 4128)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
size := hasher().Size()
|
|
||||||
counts := []int{1, 2, 3, 4, 5, 8, 16, 32, 64, 128}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, count := range counts {
|
|
||||||
max := count * size
|
|
||||||
incr := 1
|
|
||||||
for n := 0; n <= max+incr; n += incr {
|
|
||||||
err = f(hasher, data, n, count)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherReuseWithoutRelease(t *testing.T) {
|
|
||||||
testHasherReuse(1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherReuseWithRelease(t *testing.T) {
|
|
||||||
testHasherReuse(maxproccnt, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHasherReuse(i int, t *testing.T) {
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
pool := NewTreePool(hasher, 128, i)
|
|
||||||
defer pool.Drain(0)
|
|
||||||
bmt := New(pool)
|
|
||||||
|
|
||||||
for i := 0; i < 500; i++ {
|
|
||||||
n := rand.Intn(4096)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
|
|
||||||
err := testHasherCorrectness(bmt, hasher, data, n, 128)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherConcurrency(t *testing.T) {
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
pool := NewTreePool(hasher, 128, maxproccnt)
|
|
||||||
defer pool.Drain(0)
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
cycles := 100
|
|
||||||
wg.Add(maxproccnt * cycles)
|
|
||||||
errc := make(chan error)
|
|
||||||
|
|
||||||
for p := 0; p < maxproccnt; p++ {
|
|
||||||
for i := 0; i < cycles; i++ {
|
|
||||||
go func() {
|
|
||||||
bmt := New(pool)
|
|
||||||
n := rand.Intn(4096)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
err := testHasherCorrectness(bmt, hasher, data, n, 128)
|
|
||||||
wg.Done()
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(errc)
|
|
||||||
}()
|
|
||||||
var err error
|
|
||||||
select {
|
|
||||||
case <-time.NewTimer(5 * time.Second).C:
|
|
||||||
err = fmt.Errorf("timed out")
|
|
||||||
case err = <-errc:
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testBaseHasher(hasher BaseHasher, d []byte, n, count int) error {
|
|
||||||
pool := NewTreePool(hasher, count, 1)
|
|
||||||
defer pool.Drain(0)
|
|
||||||
bmt := New(pool)
|
|
||||||
return testHasherCorrectness(bmt, hasher, d, n, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHasherCorrectness(bmt hash.Hash, hasher BaseHasher, d []byte, n, count int) (err error) {
|
|
||||||
data := d[:n]
|
|
||||||
rbmt := NewRefHasher(hasher, count)
|
|
||||||
exp := rbmt.Hash(data)
|
|
||||||
timeout := time.NewTimer(time.Second)
|
|
||||||
c := make(chan error)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
bmt.Reset()
|
|
||||||
bmt.Write(data)
|
|
||||||
got := bmt.Sum(nil)
|
|
||||||
if !bytes.Equal(got, exp) {
|
|
||||||
c <- fmt.Errorf("wrong hash: expected %x, got %x", exp, got)
|
|
||||||
}
|
|
||||||
close(c)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-timeout.C:
|
|
||||||
err = fmt.Errorf("BMT hash calculation timed out")
|
|
||||||
case err = <-c:
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSHA3_4k(t *testing.B) { benchmarkSHA3(4096, t) }
|
|
||||||
func BenchmarkSHA3_2k(t *testing.B) { benchmarkSHA3(4096/2, t) }
|
|
||||||
func BenchmarkSHA3_1k(t *testing.B) { benchmarkSHA3(4096/4, t) }
|
|
||||||
func BenchmarkSHA3_512b(t *testing.B) { benchmarkSHA3(4096/8, t) }
|
|
||||||
func BenchmarkSHA3_256b(t *testing.B) { benchmarkSHA3(4096/16, t) }
|
|
||||||
func BenchmarkSHA3_128b(t *testing.B) { benchmarkSHA3(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkBMTBaseline_4k(t *testing.B) { benchmarkBMTBaseline(4096, t) }
|
|
||||||
func BenchmarkBMTBaseline_2k(t *testing.B) { benchmarkBMTBaseline(4096/2, t) }
|
|
||||||
func BenchmarkBMTBaseline_1k(t *testing.B) { benchmarkBMTBaseline(4096/4, t) }
|
|
||||||
func BenchmarkBMTBaseline_512b(t *testing.B) { benchmarkBMTBaseline(4096/8, t) }
|
|
||||||
func BenchmarkBMTBaseline_256b(t *testing.B) { benchmarkBMTBaseline(4096/16, t) }
|
|
||||||
func BenchmarkBMTBaseline_128b(t *testing.B) { benchmarkBMTBaseline(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkRefHasher_4k(t *testing.B) { benchmarkRefHasher(4096, t) }
|
|
||||||
func BenchmarkRefHasher_2k(t *testing.B) { benchmarkRefHasher(4096/2, t) }
|
|
||||||
func BenchmarkRefHasher_1k(t *testing.B) { benchmarkRefHasher(4096/4, t) }
|
|
||||||
func BenchmarkRefHasher_512b(t *testing.B) { benchmarkRefHasher(4096/8, t) }
|
|
||||||
func BenchmarkRefHasher_256b(t *testing.B) { benchmarkRefHasher(4096/16, t) }
|
|
||||||
func BenchmarkRefHasher_128b(t *testing.B) { benchmarkRefHasher(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkHasher_4k(t *testing.B) { benchmarkHasher(4096, t) }
|
|
||||||
func BenchmarkHasher_2k(t *testing.B) { benchmarkHasher(4096/2, t) }
|
|
||||||
func BenchmarkHasher_1k(t *testing.B) { benchmarkHasher(4096/4, t) }
|
|
||||||
func BenchmarkHasher_512b(t *testing.B) { benchmarkHasher(4096/8, t) }
|
|
||||||
func BenchmarkHasher_256b(t *testing.B) { benchmarkHasher(4096/16, t) }
|
|
||||||
func BenchmarkHasher_128b(t *testing.B) { benchmarkHasher(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkHasherNoReuse_4k(t *testing.B) { benchmarkHasherReuse(1, 4096, t) }
|
|
||||||
func BenchmarkHasherNoReuse_2k(t *testing.B) { benchmarkHasherReuse(1, 4096/2, t) }
|
|
||||||
func BenchmarkHasherNoReuse_1k(t *testing.B) { benchmarkHasherReuse(1, 4096/4, t) }
|
|
||||||
func BenchmarkHasherNoReuse_512b(t *testing.B) { benchmarkHasherReuse(1, 4096/8, t) }
|
|
||||||
func BenchmarkHasherNoReuse_256b(t *testing.B) { benchmarkHasherReuse(1, 4096/16, t) }
|
|
||||||
func BenchmarkHasherNoReuse_128b(t *testing.B) { benchmarkHasherReuse(1, 4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkHasherReuse_4k(t *testing.B) { benchmarkHasherReuse(16, 4096, t) }
|
|
||||||
func BenchmarkHasherReuse_2k(t *testing.B) { benchmarkHasherReuse(16, 4096/2, t) }
|
|
||||||
func BenchmarkHasherReuse_1k(t *testing.B) { benchmarkHasherReuse(16, 4096/4, t) }
|
|
||||||
func BenchmarkHasherReuse_512b(t *testing.B) { benchmarkHasherReuse(16, 4096/8, t) }
|
|
||||||
func BenchmarkHasherReuse_256b(t *testing.B) { benchmarkHasherReuse(16, 4096/16, t) }
|
|
||||||
func BenchmarkHasherReuse_128b(t *testing.B) { benchmarkHasherReuse(16, 4096/32, t) }
|
|
||||||
|
|
||||||
// benchmarks the minimum hashing time for a balanced (for simplicity) BMT
|
|
||||||
// by doing count/segmentsize parallel hashings of 2*segmentsize bytes
|
|
||||||
// doing it on n maxproccnt each reusing the base hasher
|
|
||||||
// the premise is that this is the minimum computation needed for a BMT
|
|
||||||
// therefore this serves as a theoretical optimum for concurrent implementations
|
|
||||||
func benchmarkBMTBaseline(n int, t *testing.B) {
|
|
||||||
tdata := testDataReader(64)
|
|
||||||
data := make([]byte, 64)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
count := int32((n-1)/hasher().Size() + 1)
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(maxproccnt)
|
|
||||||
var i int32
|
|
||||||
for j := 0; j < maxproccnt; j++ {
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
h := hasher()
|
|
||||||
for atomic.AddInt32(&i, 1) < count {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(data)
|
|
||||||
h.Sum(nil)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkHasher(n int, t *testing.B) {
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
|
|
||||||
size := 1
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
segmentCount := 128
|
|
||||||
pool := NewTreePool(hasher, segmentCount, size)
|
|
||||||
bmt := New(pool)
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
bmt.Reset()
|
|
||||||
bmt.Write(data)
|
|
||||||
bmt.Sum(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkHasherReuse(poolsize, n int, t *testing.B) {
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
segmentCount := 128
|
|
||||||
pool := NewTreePool(hasher, segmentCount, poolsize)
|
|
||||||
cycles := 200
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(cycles)
|
|
||||||
for j := 0; j < cycles; j++ {
|
|
||||||
bmt := New(pool)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
bmt.Reset()
|
|
||||||
bmt.Write(data)
|
|
||||||
bmt.Sum(nil)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkSHA3(n int, t *testing.B) {
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
h := hasher()
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(data)
|
|
||||||
h.Sum(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkRefHasher(n int, t *testing.B) {
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
rbmt := NewRefHasher(hasher, 128)
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
rbmt.Hash(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,37 +2,39 @@
|
||||||
|
|
||||||
Tagged releases and develop branch commits are available as installable Debian packages
|
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
|
for Ubuntu. Packages are built for the all Ubuntu versions which are supported by
|
||||||
Canonical:
|
Canonical.
|
||||||
|
|
||||||
- Trusty Tahr (14.04 LTS)
|
|
||||||
- Xenial Xerus (16.04 LTS)
|
|
||||||
- Yakkety Yak (16.10)
|
|
||||||
- Zesty Zapus (17.04)
|
|
||||||
|
|
||||||
Packages of develop branch commits have suffix -unstable and cannot be installed alongside
|
Packages of develop branch commits have suffix -unstable and cannot be installed alongside
|
||||||
the stable version. Switching between release streams requires user intervention.
|
the stable version. Switching between release streams requires user intervention.
|
||||||
|
|
||||||
|
## Launchpad
|
||||||
|
|
||||||
The packages are built and served by launchpad.net. We generate a Debian source package
|
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
|
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
|
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
|
by a team member for source package uploads.
|
||||||
variable which Travis CI makes available to certain builds.
|
|
||||||
|
The signing key is stored in an environment variable which Travis CI makes available to
|
||||||
|
certain builds. Since Travis CI doesn't support FTP, SFTP is used to transfer the
|
||||||
|
packages. To set this up yourself, you need to create a Launchpad user and add a GPG key
|
||||||
|
and SSH key to it. Then encode both keys as base64 and configure 'secret' environment
|
||||||
|
variables `PPA_SIGNING_KEY` and `PPA_SSH_KEY` on Travis.
|
||||||
|
|
||||||
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
|
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,
|
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
|
our PPA depends on the ~gophers/ubuntu/archive PPA. Our source package build-depends on
|
||||||
golang-1.9, which is co-installable alongside the regular golang package. PPA dependencies
|
golang-1.11, which is co-installable alongside the regular golang package. PPA dependencies
|
||||||
can be edited at https://launchpad.net/%7Eethereum/+archive/ubuntu/ethereum/+edit-dependencies
|
can be edited at https://launchpad.net/%7Eethereum/+archive/ubuntu/ethereum/+edit-dependencies
|
||||||
|
|
||||||
## Building Packages Locally (for testing)
|
## Building Packages Locally (for testing)
|
||||||
|
|
||||||
You need to run Ubuntu to do test packaging.
|
You need to run Ubuntu to do test packaging.
|
||||||
|
|
||||||
Add the gophers PPA and install Go 1.9 and Debian packaging tools:
|
Add the gophers PPA and install Go 1.11 and Debian packaging tools:
|
||||||
|
|
||||||
$ sudo apt-add-repository ppa:gophers/ubuntu/archive
|
$ sudo apt-add-repository ppa:gophers/ubuntu/archive
|
||||||
$ sudo apt-get update
|
$ sudo apt-get update
|
||||||
$ sudo apt-get install build-essential golang-1.9 devscripts debhelper
|
$ sudo apt-get install build-essential golang-1.11 devscripts debhelper python-bzrlib python-paramiko
|
||||||
|
|
||||||
Create the source packages:
|
Create the source packages:
|
||||||
|
|
||||||
|
|
|
||||||
306
build/ci.go
|
|
@ -26,7 +26,7 @@ Available commands are:
|
||||||
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
||||||
test [ -coverage ] [ packages... ] -- runs the tests
|
test [ -coverage ] [ packages... ] -- runs the tests
|
||||||
lint -- runs certain pre-selected linters
|
lint -- runs certain pre-selected linters
|
||||||
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts
|
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts
|
||||||
importkeys -- imports signing keys from env
|
importkeys -- imports signing keys from env
|
||||||
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
|
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
|
||||||
nsis -- creates a Windows NSIS installer
|
nsis -- creates a Windows NSIS installer
|
||||||
|
|
@ -59,6 +59,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/build"
|
"github.com/ethereum/go-ethereum/internal/build"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
sv "github.com/ethereum/go-ethereum/swarm/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -77,51 +79,89 @@ var (
|
||||||
executablePath("geth"),
|
executablePath("geth"),
|
||||||
executablePath("puppeth"),
|
executablePath("puppeth"),
|
||||||
executablePath("rlpdump"),
|
executablePath("rlpdump"),
|
||||||
executablePath("swarm"),
|
|
||||||
executablePath("wnode"),
|
executablePath("wnode"),
|
||||||
|
executablePath("clef"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files that end up in the swarm*.zip archive.
|
||||||
|
swarmArchiveFiles = []string{
|
||||||
|
"COPYING",
|
||||||
|
executablePath("swarm"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// A debian package is created for all executables listed here.
|
// A debian package is created for all executables listed here.
|
||||||
debExecutables = []debExecutable{
|
debExecutables = []debExecutable{
|
||||||
{
|
{
|
||||||
Name: "abigen",
|
BinaryName: "abigen",
|
||||||
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bootnode",
|
BinaryName: "bootnode",
|
||||||
Description: "Ethereum bootnode.",
|
Description: "Ethereum bootnode.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "evm",
|
BinaryName: "evm",
|
||||||
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "geth",
|
BinaryName: "geth",
|
||||||
Description: "Ethereum CLI client.",
|
Description: "Ethereum CLI client.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "puppeth",
|
BinaryName: "puppeth",
|
||||||
Description: "Ethereum private network manager.",
|
Description: "Ethereum private network manager.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "rlpdump",
|
BinaryName: "rlpdump",
|
||||||
Description: "Developer utility tool that prints RLP structures.",
|
Description: "Developer utility tool that prints RLP structures.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "swarm",
|
BinaryName: "wnode",
|
||||||
Description: "Ethereum Swarm daemon and tools",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "wnode",
|
|
||||||
Description: "Ethereum Whisper diagnostic tool",
|
Description: "Ethereum Whisper diagnostic tool",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
BinaryName: "clef",
|
||||||
|
Description: "Ethereum account management tool.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A debian package is created for all executables listed here.
|
||||||
|
debSwarmExecutables = []debExecutable{
|
||||||
|
{
|
||||||
|
BinaryName: "swarm",
|
||||||
|
PackageName: "ethereum-swarm",
|
||||||
|
Description: "Ethereum Swarm daemon and tools",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
debEthereum = debPackage{
|
||||||
|
Name: "ethereum",
|
||||||
|
Version: params.Version,
|
||||||
|
Executables: debExecutables,
|
||||||
|
}
|
||||||
|
|
||||||
|
debSwarm = debPackage{
|
||||||
|
Name: "ethereum-swarm",
|
||||||
|
Version: sv.Version,
|
||||||
|
Executables: debSwarmExecutables,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debian meta packages to build and push to Ubuntu PPA
|
||||||
|
debPackages = []debPackage{
|
||||||
|
debSwarm,
|
||||||
|
debEthereum,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packages to be cross-compiled by the xgo command
|
||||||
|
allCrossCompiledArchiveFiles = append(allToolsArchiveFiles, swarmArchiveFiles...)
|
||||||
|
|
||||||
// Distros for which packages are created.
|
// Distros for which packages are created.
|
||||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||||
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
||||||
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
||||||
debDistros = []string{"trusty", "xenial", "zesty", "artful"}
|
// Note: zesty is unsupported because it was officially deprecated on lanchpad.
|
||||||
|
// Note: artful is unsupported because it was officially deprecated on lanchpad.
|
||||||
|
debDistros = []string{"trusty", "xenial", "bionic", "cosmic", "disco"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
@ -181,13 +221,13 @@ func doInstall(cmdline []string) {
|
||||||
// Check Go version. People regularly open issues about compilation
|
// Check Go version. People regularly open issues about compilation
|
||||||
// failure with outdated Go. This should save them the trouble.
|
// failure with outdated Go. This should save them the trouble.
|
||||||
if !strings.Contains(runtime.Version(), "devel") {
|
if !strings.Contains(runtime.Version(), "devel") {
|
||||||
// Figure out the minor version number since we can't textually compare (1.10 < 1.7)
|
// Figure out the minor version number since we can't textually compare (1.10 < 1.9)
|
||||||
var minor int
|
var minor int
|
||||||
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
||||||
|
|
||||||
if minor < 7 {
|
if minor < 9 {
|
||||||
log.Println("You have Go version", runtime.Version())
|
log.Println("You have Go version", runtime.Version())
|
||||||
log.Println("go-ethereum requires at least Go version 1.7 and cannot")
|
log.Println("go-ethereum requires at least Go version 1.9 and cannot")
|
||||||
log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
|
log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
@ -261,16 +301,6 @@ func goTool(subcmd string, args ...string) *exec.Cmd {
|
||||||
|
|
||||||
func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd {
|
func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd {
|
||||||
cmd := build.GoTool(subcmd, args...)
|
cmd := build.GoTool(subcmd, args...)
|
||||||
if subcmd == "build" || subcmd == "install" || subcmd == "test" {
|
|
||||||
// Go CGO has a Windows linker error prior to 1.8 (https://github.com/golang/go/issues/8756).
|
|
||||||
// Work around issue by allowing multiple definitions for <1.8 builds.
|
|
||||||
var minor int
|
|
||||||
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" && minor < 8 {
|
|
||||||
cmd.Args = append(cmd.Args, []string{"-ldflags", "-extldflags -Wl,--allow-multiple-definition"}...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd.Env = []string{"GOPATH=" + build.GOPATH()}
|
cmd.Env = []string{"GOPATH=" + build.GOPATH()}
|
||||||
if arch == "" || arch == runtime.GOARCH {
|
if arch == "" || arch == runtime.GOARCH {
|
||||||
cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
|
cmd.Env = append(cmd.Env, "GOBIN="+GOBIN)
|
||||||
|
|
@ -295,9 +325,7 @@ func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd
|
||||||
// "tests" also includes static analysis tools such as vet.
|
// "tests" also includes static analysis tools such as vet.
|
||||||
|
|
||||||
func doTest(cmdline []string) {
|
func doTest(cmdline []string) {
|
||||||
var (
|
coverage := flag.Bool("coverage", false, "Whether to record code coverage")
|
||||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
|
||||||
)
|
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
env := build.Env()
|
env := build.Env()
|
||||||
|
|
||||||
|
|
@ -307,14 +335,11 @@ func doTest(cmdline []string) {
|
||||||
}
|
}
|
||||||
packages = build.ExpandPackagesNoVendor(packages)
|
packages = build.ExpandPackagesNoVendor(packages)
|
||||||
|
|
||||||
// Run analysis tools before the tests.
|
|
||||||
build.MustRun(goTool("vet", packages...))
|
|
||||||
|
|
||||||
// Run the actual tests.
|
// Run the actual tests.
|
||||||
gotest := goTool("test", buildFlags(env)...)
|
|
||||||
// Test a single package at a time. CI builders are slow
|
// Test a single package at a time. CI builders are slow
|
||||||
// and some tests run into timeouts under load.
|
// and some tests run into timeouts under load.
|
||||||
gotest.Args = append(gotest.Args, "-p", "1")
|
gotest := goTool("test", buildFlags(env)...)
|
||||||
|
gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "5m")
|
||||||
if *coverage {
|
if *coverage {
|
||||||
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
||||||
}
|
}
|
||||||
|
|
@ -338,7 +363,11 @@ func doLint(cmdline []string) {
|
||||||
// Run fast linters batched together
|
// Run fast linters batched together
|
||||||
configs := []string{
|
configs := []string{
|
||||||
"--vendor",
|
"--vendor",
|
||||||
|
"--tests",
|
||||||
|
"--deadline=2m",
|
||||||
"--disable-all",
|
"--disable-all",
|
||||||
|
"--enable=goimports",
|
||||||
|
"--enable=varcheck",
|
||||||
"--enable=vet",
|
"--enable=vet",
|
||||||
"--enable=gofmt",
|
"--enable=gofmt",
|
||||||
"--enable=misspell",
|
"--enable=misspell",
|
||||||
|
|
@ -349,13 +378,12 @@ func doLint(cmdline []string) {
|
||||||
|
|
||||||
// Run slow linters one by one
|
// Run slow linters one by one
|
||||||
for _, linter := range []string{"unconvert", "gosimple"} {
|
for _, linter := range []string{"unconvert", "gosimple"} {
|
||||||
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
configs = []string{"--vendor", "--tests", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
||||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release Packaging
|
// Release Packaging
|
||||||
|
|
||||||
func doArchive(cmdline []string) {
|
func doArchive(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||||
|
|
@ -376,9 +404,13 @@ func doArchive(cmdline []string) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
env = build.Env()
|
env = build.Env()
|
||||||
base = archiveBasename(*arch, env)
|
|
||||||
geth = "geth-" + base + ext
|
basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit))
|
||||||
alltools = "geth-alltools-" + base + ext
|
geth = "geth-" + basegeth + ext
|
||||||
|
alltools = "geth-alltools-" + basegeth + ext
|
||||||
|
|
||||||
|
baseswarm = archiveBasename(*arch, sv.ArchiveVersion(env.Commit))
|
||||||
|
swarm = "swarm-" + baseswarm + ext
|
||||||
)
|
)
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
||||||
|
|
@ -387,14 +419,17 @@ func doArchive(cmdline []string) {
|
||||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, archive := range []string{geth, alltools} {
|
if err := build.WriteArchive(swarm, swarmArchiveFiles); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, archive := range []string{geth, alltools, swarm} {
|
||||||
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveBasename(arch string, env build.Environment) string {
|
func archiveBasename(arch string, archiveVersion string) string {
|
||||||
platform := runtime.GOOS + "-" + arch
|
platform := runtime.GOOS + "-" + arch
|
||||||
if arch == "arm" {
|
if arch == "arm" {
|
||||||
platform += os.Getenv("GOARM")
|
platform += os.Getenv("GOARM")
|
||||||
|
|
@ -405,28 +440,14 @@ func archiveBasename(arch string, env build.Environment) string {
|
||||||
if arch == "ios" {
|
if arch == "ios" {
|
||||||
platform = "ios-all"
|
platform = "ios-all"
|
||||||
}
|
}
|
||||||
return platform + "-" + archiveVersion(env)
|
return platform + "-" + archiveVersion
|
||||||
}
|
|
||||||
|
|
||||||
func archiveVersion(env build.Environment) string {
|
|
||||||
version := build.VERSION()
|
|
||||||
if isUnstableBuild(env) {
|
|
||||||
version += "-unstable"
|
|
||||||
}
|
|
||||||
if env.Commit != "" {
|
|
||||||
version += "-" + env.Commit[:8]
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveUpload(archive string, blobstore string, signer string) error {
|
func archiveUpload(archive string, blobstore string, signer string) error {
|
||||||
// If signing was requested, generate the signature files
|
// If signing was requested, generate the signature files
|
||||||
if signer != "" {
|
if signer != "" {
|
||||||
pgpkey, err := base64.StdEncoding.DecodeString(os.Getenv(signer))
|
key := getenvBase64(signer)
|
||||||
if err != nil {
|
if err := build.PGPSignFile(archive, archive+".asc", string(key)); err != nil {
|
||||||
return fmt.Errorf("invalid base64 %s", signer)
|
|
||||||
}
|
|
||||||
if err := build.PGPSignFile(archive, archive+".asc", string(pgpkey)); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -466,11 +487,11 @@ func maybeSkipArchive(env build.Environment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debian Packaging
|
// Debian Packaging
|
||||||
|
|
||||||
func doDebianSource(cmdline []string) {
|
func doDebianSource(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
||||||
upload = flag.String("upload", "", `Where to upload the source package (usually "ppa:ethereum/ethereum")`)
|
upload = flag.String("upload", "", `Where to upload the source package (usually "ethereum/ethereum")`)
|
||||||
|
sshUser = flag.String("sftp-user", "", `Username for SFTP upload (usually "geth-ci")`)
|
||||||
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
workdir = flag.String("workdir", "", `Output directory for packages (uses temp dir if unset)`)
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
)
|
)
|
||||||
|
|
@ -480,33 +501,67 @@ func doDebianSource(cmdline []string) {
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
|
|
||||||
// Import the signing key.
|
// Import the signing key.
|
||||||
if b64key := os.Getenv("PPA_SIGNING_KEY"); b64key != "" {
|
if key := getenvBase64("PPA_SIGNING_KEY"); len(key) > 0 {
|
||||||
key, err := base64.StdEncoding.DecodeString(b64key)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("invalid base64 PPA_SIGNING_KEY")
|
|
||||||
}
|
|
||||||
gpg := exec.Command("gpg", "--import")
|
gpg := exec.Command("gpg", "--import")
|
||||||
gpg.Stdin = bytes.NewReader(key)
|
gpg.Stdin = bytes.NewReader(key)
|
||||||
build.MustRun(gpg)
|
build.MustRun(gpg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the packages.
|
// Create Debian packages and upload them
|
||||||
|
for _, pkg := range debPackages {
|
||||||
for _, distro := range debDistros {
|
for _, distro := range debDistros {
|
||||||
meta := newDebMetadata(distro, *signer, env, now)
|
meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
|
||||||
pkgdir := stageDebianSource(*workdir, meta)
|
pkgdir := stageDebianSource(*workdir, meta)
|
||||||
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
|
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc", "-d", "-Zxz")
|
||||||
debuild.Dir = pkgdir
|
debuild.Dir = pkgdir
|
||||||
build.MustRun(debuild)
|
build.MustRun(debuild)
|
||||||
|
|
||||||
changes := fmt.Sprintf("%s_%s_source.changes", meta.Name(), meta.VersionString())
|
var (
|
||||||
changes = filepath.Join(*workdir, changes)
|
basename = fmt.Sprintf("%s_%s", meta.Name(), meta.VersionString())
|
||||||
|
source = filepath.Join(*workdir, basename+".tar.xz")
|
||||||
|
dsc = filepath.Join(*workdir, basename+".dsc")
|
||||||
|
changes = filepath.Join(*workdir, basename+"_source.changes")
|
||||||
|
)
|
||||||
if *signer != "" {
|
if *signer != "" {
|
||||||
build.MustRunCommand("debsign", changes)
|
build.MustRunCommand("debsign", changes)
|
||||||
}
|
}
|
||||||
if *upload != "" {
|
if *upload != "" {
|
||||||
build.MustRunCommand("dput", *upload, changes)
|
ppaUpload(*workdir, *upload, *sshUser, []string{source, dsc, changes})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ppaUpload(workdir, ppa, sshUser string, files []string) {
|
||||||
|
p := strings.Split(ppa, "/")
|
||||||
|
if len(p) != 2 {
|
||||||
|
log.Fatal("-upload PPA name must contain single /")
|
||||||
|
}
|
||||||
|
if sshUser == "" {
|
||||||
|
sshUser = p[0]
|
||||||
|
}
|
||||||
|
incomingDir := fmt.Sprintf("~%s/ubuntu/%s", p[0], p[1])
|
||||||
|
// Create the SSH identity file if it doesn't exist.
|
||||||
|
var idfile string
|
||||||
|
if sshkey := getenvBase64("PPA_SSH_KEY"); len(sshkey) > 0 {
|
||||||
|
idfile = filepath.Join(workdir, "sshkey")
|
||||||
|
if _, err := os.Stat(idfile); os.IsNotExist(err) {
|
||||||
|
ioutil.WriteFile(idfile, sshkey, 0600)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Upload
|
||||||
|
dest := sshUser + "@ppa.launchpad.net"
|
||||||
|
if err := build.UploadSFTP(idfile, dest, incomingDir, files); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getenvBase64(variable string) []byte {
|
||||||
|
dec, err := base64.StdEncoding.DecodeString(os.Getenv(variable))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("invalid base64 " + variable)
|
||||||
|
}
|
||||||
|
return []byte(dec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeWorkdir(wdflag string) string {
|
func makeWorkdir(wdflag string) string {
|
||||||
|
|
@ -529,9 +584,17 @@ func isUnstableBuild(env build.Environment) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type debPackage struct {
|
||||||
|
Name string // the name of the Debian package to produce, e.g. "ethereum", or "ethereum-swarm"
|
||||||
|
Version string // the clean version of the debPackage, e.g. 1.8.12 or 0.3.0, without any metadata
|
||||||
|
Executables []debExecutable // executables to be included in the package
|
||||||
|
}
|
||||||
|
|
||||||
type debMetadata struct {
|
type debMetadata struct {
|
||||||
Env build.Environment
|
Env build.Environment
|
||||||
|
|
||||||
|
PackageName string
|
||||||
|
|
||||||
// go-ethereum version being built. Note that this
|
// go-ethereum version being built. Note that this
|
||||||
// is not the debian package version. The package version
|
// is not the debian package version. The package version
|
||||||
// is constructed by VersionString.
|
// is constructed by VersionString.
|
||||||
|
|
@ -543,21 +606,33 @@ type debMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type debExecutable struct {
|
type debExecutable struct {
|
||||||
Name, Description string
|
PackageName string
|
||||||
|
BinaryName string
|
||||||
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebMetadata(distro, author string, env build.Environment, t time.Time) debMetadata {
|
// Package returns the name of the package if present, or
|
||||||
|
// fallbacks to BinaryName
|
||||||
|
func (d debExecutable) Package() string {
|
||||||
|
if d.PackageName != "" {
|
||||||
|
return d.PackageName
|
||||||
|
}
|
||||||
|
return d.BinaryName
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
|
||||||
if author == "" {
|
if author == "" {
|
||||||
// No signing key, use default author.
|
// No signing key, use default author.
|
||||||
author = "Ethereum Builds <fjl@ethereum.org>"
|
author = "Ethereum Builds <fjl@ethereum.org>"
|
||||||
}
|
}
|
||||||
return debMetadata{
|
return debMetadata{
|
||||||
|
PackageName: name,
|
||||||
Env: env,
|
Env: env,
|
||||||
Author: author,
|
Author: author,
|
||||||
Distro: distro,
|
Distro: distro,
|
||||||
Version: build.VERSION(),
|
Version: version,
|
||||||
Time: t.Format(time.RFC1123Z),
|
Time: t.Format(time.RFC1123Z),
|
||||||
Executables: debExecutables,
|
Executables: exes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -565,9 +640,9 @@ func newDebMetadata(distro, author string, env build.Environment, t time.Time) d
|
||||||
// on all executable packages.
|
// on all executable packages.
|
||||||
func (meta debMetadata) Name() string {
|
func (meta debMetadata) Name() string {
|
||||||
if isUnstableBuild(meta.Env) {
|
if isUnstableBuild(meta.Env) {
|
||||||
return "ethereum-unstable"
|
return meta.PackageName + "-unstable"
|
||||||
}
|
}
|
||||||
return "ethereum"
|
return meta.PackageName
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionString returns the debian version of the packages.
|
// VersionString returns the debian version of the packages.
|
||||||
|
|
@ -594,9 +669,9 @@ func (meta debMetadata) ExeList() string {
|
||||||
// ExeName returns the package name of an executable package.
|
// ExeName returns the package name of an executable package.
|
||||||
func (meta debMetadata) ExeName(exe debExecutable) string {
|
func (meta debMetadata) ExeName(exe debExecutable) string {
|
||||||
if isUnstableBuild(meta.Env) {
|
if isUnstableBuild(meta.Env) {
|
||||||
return exe.Name + "-unstable"
|
return exe.Package() + "-unstable"
|
||||||
}
|
}
|
||||||
return exe.Name
|
return exe.Package()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExeConflicts returns the content of the Conflicts field
|
// ExeConflicts returns the content of the Conflicts field
|
||||||
|
|
@ -611,7 +686,7 @@ func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||||
// be preferred and the conflicting files should be handled via
|
// be preferred and the conflicting files should be handled via
|
||||||
// alternates. We might do this eventually but using a conflict is
|
// alternates. We might do this eventually but using a conflict is
|
||||||
// easier now.
|
// easier now.
|
||||||
return "ethereum, " + exe.Name
|
return "ethereum, " + exe.Package()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -628,24 +703,23 @@ func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
|
||||||
|
|
||||||
// Put the debian build files in place.
|
// Put the debian build files in place.
|
||||||
debian := filepath.Join(pkgdir, "debian")
|
debian := filepath.Join(pkgdir, "debian")
|
||||||
build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
||||||
build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
||||||
build.Render("build/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
||||||
build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
||||||
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
||||||
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
||||||
for _, exe := range meta.Executables {
|
for _, exe := range meta.Executables {
|
||||||
install := filepath.Join(debian, meta.ExeName(exe)+".install")
|
install := filepath.Join(debian, meta.ExeName(exe)+".install")
|
||||||
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
|
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
|
||||||
build.Render("build/deb.install", install, 0644, exe)
|
build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe)
|
||||||
build.Render("build/deb.docs", docs, 0644, exe)
|
build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgdir
|
return pkgdir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows installer
|
// Windows installer
|
||||||
|
|
||||||
func doWindowsInstaller(cmdline []string) {
|
func doWindowsInstaller(cmdline []string) {
|
||||||
// Parse the flags and make skip installer generation on PRs
|
// Parse the flags and make skip installer generation on PRs
|
||||||
var (
|
var (
|
||||||
|
|
@ -695,11 +769,11 @@ func doWindowsInstaller(cmdline []string) {
|
||||||
// Build the installer. This assumes that all the needed files have been previously
|
// Build the installer. This assumes that all the needed files have been previously
|
||||||
// built (don't mix building and packaging to keep cross compilation complexity to a
|
// built (don't mix building and packaging to keep cross compilation complexity to a
|
||||||
// minimum).
|
// minimum).
|
||||||
version := strings.Split(build.VERSION(), ".")
|
version := strings.Split(params.Version, ".")
|
||||||
if env.Commit != "" {
|
if env.Commit != "" {
|
||||||
version[2] += "-" + env.Commit[:8]
|
version[2] += "-" + env.Commit[:8]
|
||||||
}
|
}
|
||||||
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, env) + ".exe")
|
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe")
|
||||||
build.MustRunCommand("makensis.exe",
|
build.MustRunCommand("makensis.exe",
|
||||||
"/DOUTPUTFILE="+installer,
|
"/DOUTPUTFILE="+installer,
|
||||||
"/DMAJORVERSION="+version[0],
|
"/DMAJORVERSION="+version[0],
|
||||||
|
|
@ -731,13 +805,9 @@ func doAndroidArchive(cmdline []string) {
|
||||||
if os.Getenv("ANDROID_HOME") == "" {
|
if os.Getenv("ANDROID_HOME") == "" {
|
||||||
log.Fatal("Please ensure ANDROID_HOME points to your Android SDK")
|
log.Fatal("Please ensure ANDROID_HOME points to your Android SDK")
|
||||||
}
|
}
|
||||||
if os.Getenv("ANDROID_NDK") == "" {
|
|
||||||
log.Fatal("Please ensure ANDROID_NDK points to your Android NDK")
|
|
||||||
}
|
|
||||||
// Build the Android archive and Maven resources
|
// Build the Android archive and Maven resources
|
||||||
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile"))
|
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
||||||
build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK")))
|
build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
|
||||||
build.MustRun(gomobileTool("bind", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
|
|
||||||
|
|
||||||
if *local {
|
if *local {
|
||||||
// If we're building locally, copy bundle to build dir and skip Maven
|
// If we're building locally, copy bundle to build dir and skip Maven
|
||||||
|
|
@ -751,7 +821,7 @@ func doAndroidArchive(cmdline []string) {
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
|
|
||||||
// Sign and upload the archive to Azure
|
// Sign and upload the archive to Azure
|
||||||
archive := "geth-" + archiveBasename("android", env) + ".aar"
|
archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar"
|
||||||
os.Rename("geth.aar", archive)
|
os.Rename("geth.aar", archive)
|
||||||
|
|
||||||
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
||||||
|
|
@ -761,22 +831,22 @@ func doAndroidArchive(cmdline []string) {
|
||||||
os.Rename(archive, meta.Package+".aar")
|
os.Rename(archive, meta.Package+".aar")
|
||||||
if *signer != "" && *deploy != "" {
|
if *signer != "" && *deploy != "" {
|
||||||
// Import the signing key into the local GPG instance
|
// Import the signing key into the local GPG instance
|
||||||
if b64key := os.Getenv(*signer); b64key != "" {
|
key := getenvBase64(*signer)
|
||||||
key, err := base64.StdEncoding.DecodeString(b64key)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("invalid base64 %s", *signer)
|
|
||||||
}
|
|
||||||
gpg := exec.Command("gpg", "--import")
|
gpg := exec.Command("gpg", "--import")
|
||||||
gpg.Stdin = bytes.NewReader(key)
|
gpg.Stdin = bytes.NewReader(key)
|
||||||
build.MustRun(gpg)
|
build.MustRun(gpg)
|
||||||
|
keyID, err := build.PGPKeyID(string(key))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// Upload the artifacts to Sonatype and/or Maven Central
|
// Upload the artifacts to Sonatype and/or Maven Central
|
||||||
repo := *deploy + "/service/local/staging/deploy/maven2"
|
repo := *deploy + "/service/local/staging/deploy/maven2"
|
||||||
if meta.Develop {
|
if meta.Develop {
|
||||||
repo = *deploy + "/content/repositories/snapshots"
|
repo = *deploy + "/content/repositories/snapshots"
|
||||||
}
|
}
|
||||||
build.MustRunCommand("mvn", "gpg:sign-and-deploy-file",
|
build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X",
|
||||||
"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
|
"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
|
||||||
|
"-Dgpg.keyname="+keyID,
|
||||||
"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
|
"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -786,9 +856,10 @@ func gomobileTool(subcmd string, args ...string) *exec.Cmd {
|
||||||
cmd.Args = append(cmd.Args, args...)
|
cmd.Args = append(cmd.Args, args...)
|
||||||
cmd.Env = []string{
|
cmd.Env = []string{
|
||||||
"GOPATH=" + build.GOPATH(),
|
"GOPATH=" + build.GOPATH(),
|
||||||
|
"PATH=" + GOBIN + string(os.PathListSeparator) + os.Getenv("PATH"),
|
||||||
}
|
}
|
||||||
for _, e := range os.Environ() {
|
for _, e := range os.Environ() {
|
||||||
if strings.HasPrefix(e, "GOPATH=") {
|
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "PATH=") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmd.Env = append(cmd.Env, e)
|
cmd.Env = append(cmd.Env, e)
|
||||||
|
|
@ -830,7 +901,7 @@ func newMavenMetadata(env build.Environment) mavenMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Render the version and package strings
|
// Render the version and package strings
|
||||||
version := build.VERSION()
|
version := params.Version
|
||||||
if isUnstableBuild(env) {
|
if isUnstableBuild(env) {
|
||||||
version += "-SNAPSHOT"
|
version += "-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|
@ -855,9 +926,9 @@ func doXCodeFramework(cmdline []string) {
|
||||||
env := build.Env()
|
env := build.Env()
|
||||||
|
|
||||||
// Build the iOS XCode framework
|
// Build the iOS XCode framework
|
||||||
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile"))
|
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
||||||
build.MustRun(gomobileTool("init"))
|
build.MustRun(gomobileTool("init"))
|
||||||
bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "-v", "github.com/ethereum/go-ethereum/mobile")
|
bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "--tags", "ios", "-v", "github.com/ethereum/go-ethereum/mobile")
|
||||||
|
|
||||||
if *local {
|
if *local {
|
||||||
// If we're building locally, use the build folder and stop afterwards
|
// If we're building locally, use the build folder and stop afterwards
|
||||||
|
|
@ -865,7 +936,7 @@ func doXCodeFramework(cmdline []string) {
|
||||||
build.MustRun(bind)
|
build.MustRun(bind)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
archive := "geth-" + archiveBasename("ios", env)
|
archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit))
|
||||||
if err := os.Mkdir(archive, os.ModePerm); err != nil {
|
if err := os.Mkdir(archive, os.ModePerm); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -921,7 +992,7 @@ func newPodMetadata(env build.Environment, archive string) podMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version := build.VERSION()
|
version := params.Version
|
||||||
if isUnstableBuild(env) {
|
if isUnstableBuild(env) {
|
||||||
version += "-unstable." + env.Buildnum
|
version += "-unstable." + env.Buildnum
|
||||||
}
|
}
|
||||||
|
|
@ -951,7 +1022,7 @@ func doXgo(cmdline []string) {
|
||||||
|
|
||||||
if *alltools {
|
if *alltools {
|
||||||
args = append(args, []string{"--dest", GOBIN}...)
|
args = append(args, []string{"--dest", GOBIN}...)
|
||||||
for _, res := range allToolsArchiveFiles {
|
for _, res := range allCrossCompiledArchiveFiles {
|
||||||
if strings.HasPrefix(res, GOBIN) {
|
if strings.HasPrefix(res, GOBIN) {
|
||||||
// Binary tool found, cross build it explicitly
|
// Binary tool found, cross build it explicitly
|
||||||
args = append(args, "./"+filepath.Join("cmd", filepath.Base(res)))
|
args = append(args, "./"+filepath.Join("cmd", filepath.Base(res)))
|
||||||
|
|
@ -990,7 +1061,7 @@ func xgoTool(args []string) *exec.Cmd {
|
||||||
func doPurge(cmdline []string) {
|
func doPurge(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`)
|
store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`)
|
||||||
limit = flag.Int("days", 30, `Age threshold above which to delete unstalbe archives`)
|
limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`)
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
|
|
||||||
|
|
@ -1017,23 +1088,14 @@ func doPurge(cmdline []string) {
|
||||||
}
|
}
|
||||||
for i := 0; i < len(blobs); i++ {
|
for i := 0; i < len(blobs); i++ {
|
||||||
for j := i + 1; j < len(blobs); j++ {
|
for j := i + 1; j < len(blobs); j++ {
|
||||||
iTime, err := time.Parse(time.RFC1123, blobs[i].Properties.LastModified)
|
if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
jTime, err := time.Parse(time.RFC1123, blobs[j].Properties.LastModified)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if iTime.After(jTime) {
|
|
||||||
blobs[i], blobs[j] = blobs[j], blobs[i]
|
blobs[i], blobs[j] = blobs[j], blobs[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Filter out all archives more recent that the given threshold
|
// Filter out all archives more recent that the given threshold
|
||||||
for i, blob := range blobs {
|
for i, blob := range blobs {
|
||||||
timestamp, _ := time.Parse(time.RFC1123, blob.Properties.LastModified)
|
if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour {
|
||||||
if time.Since(timestamp) < time.Duration(*limit)*24*time.Hour {
|
|
||||||
blobs = blobs[:i]
|
blobs = blobs[:i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
build/clean_go_build_cache.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Cleaning the Go cache only makes sense if we actually have Go installed... or
|
||||||
|
# if Go is actually callable. This does not hold true during deb packaging, so
|
||||||
|
# we need an explicit check to avoid build failures.
|
||||||
|
if ! command -v go > /dev/null; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
version_gt() {
|
||||||
|
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
golang_version=$(go version |cut -d' ' -f3 |sed 's/go//')
|
||||||
|
|
||||||
|
# Clean go build cache when go version is greater than or equal to 1.10
|
||||||
|
if !(version_gt 1.10 $golang_version); then
|
||||||
|
go clean -cache
|
||||||
|
fi
|
||||||
|
|
@ -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.9/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 $@
|
|
||||||
19
build/deb/ethereum-swarm/deb.control
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Source: {{.Name}}
|
||||||
|
Section: science
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: {{.Author}}
|
||||||
|
Build-Depends: debhelper (>= 8.0.0), golang-1.11
|
||||||
|
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
|
||||||
|
|
||||||
|
{{range .Executables}}
|
||||||
|
Package: {{$.ExeName .}}
|
||||||
|
Conflicts: {{$.ExeConflicts .}}
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Built-Using: ${misc:Built-Using}
|
||||||
|
Description: {{.Description}}
|
||||||
|
{{.Description}}
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright 2016 The go-ethereum Authors
|
Copyright 2018 The go-ethereum Authors
|
||||||
|
|
||||||
go-ethereum is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
1
build/deb/ethereum-swarm/deb.install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build/bin/{{.BinaryName}} usr/bin
|
||||||
16
build/deb/ethereum-swarm/deb.rules
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
# Launchpad rejects Go's access to $HOME/.cache, use custom folder
|
||||||
|
export GOCACHE=/tmp/go-build
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
build/env.sh /usr/lib/go-1.11/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 $@
|
||||||
5
build/deb/ethereum/deb.changelog
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||||
|
|
||||||
|
* git build of {{.Env.Commit}}
|
||||||
|
|
||||||
|
-- {{.Author}} {{.Time}}
|
||||||
|
|
@ -2,7 +2,7 @@ Source: {{.Name}}
|
||||||
Section: science
|
Section: science
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: {{.Author}}
|
Maintainer: {{.Author}}
|
||||||
Build-Depends: debhelper (>= 8.0.0), golang-1.9
|
Build-Depends: debhelper (>= 8.0.0), golang-1.11
|
||||||
Standards-Version: 3.9.5
|
Standards-Version: 3.9.5
|
||||||
Homepage: https://ethereum.org
|
Homepage: https://ethereum.org
|
||||||
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||||
|
|
@ -11,8 +11,8 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
Package: {{.Name}}
|
Package: {{.Name}}
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${misc:Depends}, {{.ExeList}}
|
Depends: ${misc:Depends}, {{.ExeList}}
|
||||||
Description: Meta-package to install geth and other tools
|
Description: Meta-package to install geth, swarm, and other tools
|
||||||
Meta-package to install geth and other tools
|
Meta-package to install geth, swarm and other tools
|
||||||
|
|
||||||
{{range .Executables}}
|
{{range .Executables}}
|
||||||
Package: {{$.ExeName .}}
|
Package: {{$.ExeName .}}
|
||||||
14
build/deb/ethereum/deb.copyright
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Copyright 2018 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
build/deb/ethereum/deb.docs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
AUTHORS
|
||||||
1
build/deb/ethereum/deb.install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build/bin/{{.BinaryName}} usr/bin
|
||||||
16
build/deb/ethereum/deb.rules
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
# -*- makefile -*-
|
||||||
|
|
||||||
|
# Uncomment this to turn on verbose mode.
|
||||||
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
|
# Launchpad rejects Go's access to $HOME/.cache, use custom folder
|
||||||
|
export GOCACHE=/tmp/go-build
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
build/env.sh /usr/lib/go-1.11/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 $@
|
||||||
18
build/goimports.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/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
|
||||||
46
build/travis_keepalive.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# travis_keepalive runs the given command and preserves its return value,
|
||||||
|
# while it forks a child process what periodically produces a log line,
|
||||||
|
# so that Travis won't abort the build after 10 minutes.
|
||||||
|
|
||||||
|
# Why?
|
||||||
|
# `t.Log()` in Go holds the buffer until the test does not pass or fail,
|
||||||
|
# and `-race` can increase the execution time by 2-20x.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
readonly KEEPALIVE_INTERVAL=300 # seconds => 5m
|
||||||
|
|
||||||
|
main() {
|
||||||
|
keepalive
|
||||||
|
$@
|
||||||
|
}
|
||||||
|
|
||||||
|
# Keepalive produces a log line in each KEEPALIVE_INTERVAL.
|
||||||
|
keepalive() {
|
||||||
|
local child_pid
|
||||||
|
# Note: We fork here!
|
||||||
|
repeat "keepalive" &
|
||||||
|
child_pid=$!
|
||||||
|
ensureChildOnEXIT "${child_pid}"
|
||||||
|
}
|
||||||
|
|
||||||
|
repeat() {
|
||||||
|
local this="$1"
|
||||||
|
while true; do
|
||||||
|
echo "${this}"
|
||||||
|
sleep "${KEEPALIVE_INTERVAL}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensures that the child gets killed on normal program exit.
|
||||||
|
ensureChildOnEXIT() {
|
||||||
|
# Note: SIGINT and SIGTERM are forwarded to the child process by Bash
|
||||||
|
# automatically, so we don't have to deal with signals.
|
||||||
|
|
||||||
|
local child_pid="$1"
|
||||||
|
trap "kill ${child_pid}" EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
@ -1,3 +1,19 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
// +build none
|
// +build none
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -55,10 +71,9 @@ var (
|
||||||
"crypto/sha3/",
|
"crypto/sha3/",
|
||||||
"internal/jsre/deps",
|
"internal/jsre/deps",
|
||||||
"log/",
|
"log/",
|
||||||
|
"common/bitutil/bitutil",
|
||||||
// don't license generated files
|
// don't license generated files
|
||||||
"contracts/chequebook/contract/",
|
"contracts/chequebook/contract/code.go",
|
||||||
"contracts/ens/contract/",
|
|
||||||
"contracts/release/contract.go",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// paths with this prefix are licensed as GPL. all other files are LGPL.
|
// paths with this prefix are licensed as GPL. all other files are LGPL.
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind")
|
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind, - for STDIN")
|
||||||
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)")
|
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)")
|
||||||
typFlag = flag.String("type", "", "Struct name for the binding (default = package name)")
|
typFlag = flag.String("type", "", "Struct name for the binding (default = package name)")
|
||||||
|
|
||||||
|
|
@ -75,17 +75,28 @@ func main() {
|
||||||
bins []string
|
bins []string
|
||||||
types []string
|
types []string
|
||||||
)
|
)
|
||||||
if *solFlag != "" {
|
if *solFlag != "" || (*abiFlag == "-" && *pkgFlag == "") {
|
||||||
// Generate the list of types to exclude from binding
|
// Generate the list of types to exclude from binding
|
||||||
exclude := make(map[string]bool)
|
exclude := make(map[string]bool)
|
||||||
for _, kind := range strings.Split(*excFlag, ",") {
|
for _, kind := range strings.Split(*excFlag, ",") {
|
||||||
exclude[strings.ToLower(kind)] = true
|
exclude[strings.ToLower(kind)] = true
|
||||||
}
|
}
|
||||||
contracts, err := compiler.CompileSolidity(*solcFlag, *solFlag)
|
|
||||||
|
var contracts map[string]*compiler.Contract
|
||||||
|
var err error
|
||||||
|
if *solFlag != "" {
|
||||||
|
contracts, err = compiler.CompileSolidity(*solcFlag, *solFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to build Solidity contract: %v\n", err)
|
fmt.Printf("Failed to build Solidity contract: %v\n", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
contracts, err = contractsFromStdin()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to read input ABIs from STDIN: %v\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Gather all non-excluded contract for binding
|
// Gather all non-excluded contract for binding
|
||||||
for name, contract := range contracts {
|
for name, contract := range contracts {
|
||||||
if exclude[strings.ToLower(name)] {
|
if exclude[strings.ToLower(name)] {
|
||||||
|
|
@ -100,14 +111,20 @@ func main() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise load up the ABI, optional bytecode and type name from the parameters
|
// Otherwise load up the ABI, optional bytecode and type name from the parameters
|
||||||
abi, err := ioutil.ReadFile(*abiFlag)
|
var abi []byte
|
||||||
|
var err error
|
||||||
|
if *abiFlag == "-" {
|
||||||
|
abi, err = ioutil.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
abi, err = ioutil.ReadFile(*abiFlag)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to read input ABI: %v\n", err)
|
fmt.Printf("Failed to read input ABI: %v\n", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
abis = append(abis, string(abi))
|
abis = append(abis, string(abi))
|
||||||
|
|
||||||
bin := []byte{}
|
var bin []byte
|
||||||
if *binFlag != "" {
|
if *binFlag != "" {
|
||||||
if bin, err = ioutil.ReadFile(*binFlag); err != nil {
|
if bin, err = ioutil.ReadFile(*binFlag); err != nil {
|
||||||
fmt.Printf("Failed to read input bytecode: %v\n", err)
|
fmt.Printf("Failed to read input bytecode: %v\n", err)
|
||||||
|
|
@ -138,3 +155,11 @@ func main() {
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contractsFromStdin() (map[string]*compiler.Contract, error) {
|
||||||
|
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return compiler.ParseCombinedJSON(bytes, "", "", "", "")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
)
|
)
|
||||||
|
|
@ -37,7 +38,7 @@ func main() {
|
||||||
var (
|
var (
|
||||||
listenAddr = flag.String("addr", ":30301", "listen address")
|
listenAddr = flag.String("addr", ":30301", "listen address")
|
||||||
genKey = flag.String("genkey", "", "generate a node key")
|
genKey = flag.String("genkey", "", "generate a node key")
|
||||||
writeAddr = flag.Bool("writeaddress", false, "write out the node's pubkey hash and quit")
|
writeAddr = flag.Bool("writeaddress", false, "write out the node's public key and quit")
|
||||||
nodeKeyFile = flag.String("nodekey", "", "private key filename")
|
nodeKeyFile = flag.String("nodekey", "", "private key filename")
|
||||||
nodeKeyHex = flag.String("nodekeyhex", "", "private key as hex (for testing)")
|
nodeKeyHex = flag.String("nodekeyhex", "", "private key as hex (for testing)")
|
||||||
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)")
|
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)")
|
||||||
|
|
@ -85,7 +86,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *writeAddr {
|
if *writeAddr {
|
||||||
fmt.Printf("%v\n", discover.PubkeyID(&nodeKey.PublicKey))
|
fmt.Printf("%x\n", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:])
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,21 +112,38 @@ func main() {
|
||||||
if !realaddr.IP.IsLoopback() {
|
if !realaddr.IP.IsLoopback() {
|
||||||
go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
|
go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
|
||||||
}
|
}
|
||||||
// TODO: react to external IP changes over time.
|
|
||||||
if ext, err := natm.ExternalIP(); err == nil {
|
if ext, err := natm.ExternalIP(); err == nil {
|
||||||
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
|
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printNotice(&nodeKey.PublicKey, *realaddr)
|
||||||
|
|
||||||
if *runv5 {
|
if *runv5 {
|
||||||
if _, err := discv5.ListenUDP(nodeKey, conn, realaddr, "", restrictList); err != nil {
|
if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil {
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := discover.ListenUDP(nodeKey, conn, realaddr, nil, "", restrictList); err != nil {
|
db, _ := enode.OpenDB("")
|
||||||
|
ln := enode.NewLocalNode(db, nodeKey)
|
||||||
|
cfg := discover.Config{
|
||||||
|
PrivateKey: nodeKey,
|
||||||
|
NetRestrict: restrictList,
|
||||||
|
}
|
||||||
|
if _, err := discover.ListenUDP(conn, ln, cfg); err != nil {
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) {
|
||||||
|
if addr.IP.IsUnspecified() {
|
||||||
|
addr.IP = net.IP{127, 0, 0, 1}
|
||||||
|
}
|
||||||
|
n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port)
|
||||||
|
fmt.Println(n.String())
|
||||||
|
fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
|
||||||
|
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
|
||||||
|
}
|
||||||
|
|
|
||||||
1
cmd/clef/4byte.json
Normal file
991
cmd/clef/README.md
Normal file
|
|
@ -0,0 +1,991 @@
|
||||||
|
Clef
|
||||||
|
----
|
||||||
|
Clef can be used to sign transactions and data and is meant as a replacement for geth's account management.
|
||||||
|
This allows DApps not to depend on geth's account management. When a DApp wants to sign data it can send the data to
|
||||||
|
the signer, the signer will then provide the user with context and asks the user for permission to sign the data. If
|
||||||
|
the users grants the signing request the signer will send the signature back to the DApp.
|
||||||
|
|
||||||
|
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can
|
||||||
|
help in situations when a DApp is connected to a remote node because a local Ethereum node is not available, not
|
||||||
|
synchronised with the chain or a particular Ethereum node that has no built-in (or limited) account management.
|
||||||
|
|
||||||
|
Clef can run as a daemon on the same machine, or off a usb-stick like [usb armory](https://inversepath.com/usbarmory),
|
||||||
|
or a separate VM in a [QubesOS](https://www.qubes-os.org/) type os setup.
|
||||||
|
|
||||||
|
Check out
|
||||||
|
|
||||||
|
* the [tutorial](tutorial.md) for some concrete examples on how the signer works.
|
||||||
|
* the [setup docs](docs/setup.md) for some information on how to configure it to work on QubesOS or USBArmory.
|
||||||
|
* the [data types](datatypes.md) for detailed information on the json types used in the communication between
|
||||||
|
clef and an external UI
|
||||||
|
|
||||||
|
## Command line flags
|
||||||
|
Clef accepts the following command line options:
|
||||||
|
```
|
||||||
|
COMMANDS:
|
||||||
|
init Initialize the signer, generate secret storage
|
||||||
|
attest Attest that a js-file is to be used
|
||||||
|
setpw Store a credential for a keystore file
|
||||||
|
gendoc Generate documentation about json-rpc format
|
||||||
|
help Shows a list of commands or help for one command
|
||||||
|
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
--loglevel value log level to emit to the screen (default: 4)
|
||||||
|
--keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore")
|
||||||
|
--configdir value Directory for Clef configuration (default: "$HOME/.clef")
|
||||||
|
--chainid value Chain id to use for signing (1=mainnet, 3=ropsten, 4=rinkeby, 5=Goerli) (default: 1)
|
||||||
|
--lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength
|
||||||
|
--nousb Disables monitoring for and managing USB hardware wallets
|
||||||
|
--rpcaddr value HTTP-RPC server listening interface (default: "localhost")
|
||||||
|
--rpcvhosts value Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard. (default: "localhost")
|
||||||
|
--ipcdisable Disable the IPC-RPC server
|
||||||
|
--ipcpath Filename for IPC socket/pipe within the datadir (explicit paths escape it)
|
||||||
|
--rpc Enable the HTTP-RPC server
|
||||||
|
--rpcport value HTTP-RPC server listening port (default: 8550)
|
||||||
|
--signersecret value A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash
|
||||||
|
--4bytedb value File containing 4byte-identifiers (default: "./4byte.json")
|
||||||
|
--4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json")
|
||||||
|
--auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log")
|
||||||
|
--rules value Enable rule-engine (default: "rules.json")
|
||||||
|
--stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when Clef is started by an external process.
|
||||||
|
--stdio-ui-test Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.
|
||||||
|
--advanced If enabled, issues warnings instead of rejections for suspicious requests. Default off
|
||||||
|
--help, -h show help
|
||||||
|
--version, -v print the version
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
signer -keystore /my/keystore -chainid 4
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Security model
|
||||||
|
|
||||||
|
The security model of the signer is as follows:
|
||||||
|
|
||||||
|
* One critical component (the signer binary / daemon) is responsible for handling cryptographic operations: signing, private keys, encryption/decryption of keystore files.
|
||||||
|
* The signer binary has a well-defined 'external' API.
|
||||||
|
* The 'external' API is considered UNTRUSTED.
|
||||||
|
* The signer binary also communicates with whatever process that invoked the binary, via stdin/stdout.
|
||||||
|
* This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated.
|
||||||
|
|
||||||
|
The general flow for signing a transaction using e.g. geth is as follows:
|
||||||
|

|
||||||
|
|
||||||
|
In this case, `geth` would be started with `--externalsigner=http://localhost:8550` and would relay requests to `eth.sendTransaction`.
|
||||||
|
|
||||||
|
## TODOs
|
||||||
|
|
||||||
|
Some snags and todos
|
||||||
|
|
||||||
|
* [ ] The signer should take a startup param "--no-change", for UIs that do not contain the capability
|
||||||
|
to perform changes to things, only approve/deny. Such a UI should be able to start the signer in
|
||||||
|
a more secure mode by telling it that it only wants approve/deny capabilities.
|
||||||
|
|
||||||
|
* [x] It would be nice if the signer could collect new 4byte-id:s/method selectors, and have a
|
||||||
|
secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for
|
||||||
|
inclusion upstream.
|
||||||
|
|
||||||
|
* It should be possible to configure the signer to check if an account is indeed known to it, before
|
||||||
|
passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate
|
||||||
|
accounts if it immediately returned "unknown account".
|
||||||
|
* [x] It should be possible to configure the signer to auto-allow listing (certain) accounts, instead of asking every time.
|
||||||
|
* [x] Done Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode),
|
||||||
|
invoking methods with the following info:
|
||||||
|
* [x] Version info about the signer
|
||||||
|
* [x] Address of API (http/ipc)
|
||||||
|
* [ ] List of known accounts
|
||||||
|
* [ ] Have a default timeout on signing operations, so that if the user has not answered within e.g. 60 seconds, the request is rejected.
|
||||||
|
* [ ] `account_signRawTransaction`
|
||||||
|
* [ ] `account_bulkSignTransactions([] transactions)` should
|
||||||
|
* only exist if enabled via config/flag
|
||||||
|
* only allow non-data-sending transactions
|
||||||
|
* all txs must use the same `from`-account
|
||||||
|
* let the user confirm, showing
|
||||||
|
* the total amount
|
||||||
|
* the number of unique recipients
|
||||||
|
|
||||||
|
* Geth todos
|
||||||
|
- The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is
|
||||||
|
put together is a bit of a hack into the http server. This could probably be greatly improved
|
||||||
|
- Relay: Geth should be started in `geth --external_signer localhost:8550`.
|
||||||
|
- Currently, the Geth APIs use `common.Address` in the arguments to transaction submission (e.g `to` field). This
|
||||||
|
type is 20 `bytes`, and is incapable of carrying checksum information. The signer uses `common.MixedcaseAddress`, which
|
||||||
|
retains the original input.
|
||||||
|
- The Geth api should switch to use the same type, and relay `to`-account verbatim to the external api.
|
||||||
|
|
||||||
|
* [x] Storage
|
||||||
|
* [x] An encrypted key-value storage should be implemented
|
||||||
|
* See [rules.md](rules.md) for more info about this.
|
||||||
|
|
||||||
|
* Another potential thing to introduce is pairing.
|
||||||
|
* To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API).
|
||||||
|
* Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests.
|
||||||
|
* This feature would make the addition of rules less dangerous.
|
||||||
|
|
||||||
|
* Wallets / accounts. Add API methods for wallets.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
### External API
|
||||||
|
|
||||||
|
The signer listens to HTTP requests on `rpcaddr`:`rpcport`, with the same JSONRPC standard as Geth. The messages are
|
||||||
|
expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
||||||
|
|
||||||
|
Some of these call can require user interaction. Clients must be aware that responses
|
||||||
|
may be delayed significantly or may never be received if a users decides to ignore the confirmation request.
|
||||||
|
|
||||||
|
The External API is **untrusted** : it does not accept credentials over this api, nor does it expect
|
||||||
|
that requests have any authority.
|
||||||
|
|
||||||
|
### UI API
|
||||||
|
|
||||||
|
The signer has one native console-based UI, for operation without any standalone tools.
|
||||||
|
However, there is also an API to communicate with an external UI. To enable that UI,
|
||||||
|
the signer needs to be executed with the `--stdio-ui` option, which allocates the
|
||||||
|
`stdin`/`stdout` for the UI-api.
|
||||||
|
|
||||||
|
An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`.
|
||||||
|
|
||||||
|
The model is as follows:
|
||||||
|
|
||||||
|
* The user starts the UI app (`pythonsigner.py`).
|
||||||
|
* The UI app starts the `signer` with `--stdio-ui`, and listens to the
|
||||||
|
process output for confirmation-requests.
|
||||||
|
* The `signer` opens the external http api.
|
||||||
|
* When the `signer` receives requests, it sends a `jsonrpc` request via `stdout`.
|
||||||
|
* The UI app prompts the user accordingly, and responds to the `signer`
|
||||||
|
* The `signer` signs (or not), and responds to the original request.
|
||||||
|
|
||||||
|
## External API
|
||||||
|
|
||||||
|
See the [external api changelog](extapi_changelog.md) for information about changes to this API.
|
||||||
|
|
||||||
|
### Encoding
|
||||||
|
- number: positive integers that are hex encoded
|
||||||
|
- data: hex encoded data
|
||||||
|
- string: ASCII string
|
||||||
|
|
||||||
|
All hex encoded values must be prefixed with `0x`.
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### account_new
|
||||||
|
|
||||||
|
#### Create new password protected account
|
||||||
|
|
||||||
|
The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory.
|
||||||
|
The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- address [string]: account address that is derived from the generated key
|
||||||
|
- url [string]: location of the keyfile
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_new",
|
||||||
|
"params": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133",
|
||||||
|
"url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_list
|
||||||
|
|
||||||
|
#### List available accounts
|
||||||
|
List all accounts that this signer currently manages
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- array with account records:
|
||||||
|
- account.address [string]: account address that is derived from the generated key
|
||||||
|
- account.type [string]: type of the
|
||||||
|
- account.url [string]: location of the account
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_list"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"address": "0xafb2f771f58513609765698f65d3f2f0224a956f",
|
||||||
|
"type": "account",
|
||||||
|
"url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133",
|
||||||
|
"type": "account",
|
||||||
|
"url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_signTransaction
|
||||||
|
|
||||||
|
#### Sign transactions
|
||||||
|
Signs a transactions and responds with the signed transaction in RLP encoded form.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
2. transaction object:
|
||||||
|
- `from` [address]: account to send the transaction from
|
||||||
|
- `to` [address]: receiver account. If omitted or `0x`, will cause contract creation.
|
||||||
|
- `gas` [number]: maximum amount of gas to burn
|
||||||
|
- `gasPrice` [number]: gas price
|
||||||
|
- `value` [number:optional]: amount of Wei to send with the transaction
|
||||||
|
- `data` [data:optional]: input data
|
||||||
|
- `nonce` [number]: account nonce
|
||||||
|
3. method signature [string:optional]
|
||||||
|
- The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected.
|
||||||
|
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- signed transaction in RLP encoded form [data]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_signTransaction",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"from": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
|
||||||
|
"gas": "0x55555",
|
||||||
|
"gasPrice": "0x1234",
|
||||||
|
"input": "0xabcd",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"value": "0x1234"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"error": {
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Request denied"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### Sample call with ABI-data
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 67,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_signTransaction",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"from": "0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"value": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"
|
||||||
|
},
|
||||||
|
"safeSend(address)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 67,
|
||||||
|
"result": {
|
||||||
|
"raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||||
|
"tx": {
|
||||||
|
"nonce": "0x0",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"gas": "0x333",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"value": "0x0",
|
||||||
|
"input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||||
|
"v": "0x26",
|
||||||
|
"r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e",
|
||||||
|
"s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||||
|
"hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bash example:
|
||||||
|
```bash
|
||||||
|
#curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
|
{"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_signData
|
||||||
|
|
||||||
|
#### Sign data
|
||||||
|
Signs a chunk of data and returns the calculated signature.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- content type [string]: type of signed data
|
||||||
|
- `text/validator`: hex data with custom validator defined in a contract
|
||||||
|
- `application/clique`: [clique](https://github.com/ethereum/EIPs/issues/225) headers
|
||||||
|
- `text/plain`: simple hex data validated by `account_ecRecover`
|
||||||
|
- account [address]: account to sign with
|
||||||
|
- data [object]: data to sign
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- calculated signature [data]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_signData",
|
||||||
|
"params": [
|
||||||
|
"data/plain",
|
||||||
|
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
|
||||||
|
"0xaabbccdd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_signTypedData
|
||||||
|
|
||||||
|
#### Sign data
|
||||||
|
Signs a chunk of structured data conformant to [EIP712]([EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md)) and returns the calculated signature.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- account [address]: account to sign with
|
||||||
|
- data [object]: data to sign
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- calculated signature [data]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 68,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_signTypedData",
|
||||||
|
"params": [
|
||||||
|
"0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826",
|
||||||
|
{
|
||||||
|
"types": {
|
||||||
|
"EIP712Domain": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "version",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chainId",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "verifyingContract",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Person": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "wallet",
|
||||||
|
"type": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Mail": [
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"type": "Person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "Person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "contents",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"primaryType": "Mail",
|
||||||
|
"domain": {
|
||||||
|
"name": "Ether Mail",
|
||||||
|
"version": "1",
|
||||||
|
"chainId": 1,
|
||||||
|
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"from": {
|
||||||
|
"name": "Cow",
|
||||||
|
"wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"name": "Bob",
|
||||||
|
"wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"
|
||||||
|
},
|
||||||
|
"contents": "Hello, Bob!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_ecRecover
|
||||||
|
|
||||||
|
#### Sign data
|
||||||
|
|
||||||
|
Derive the address from the account that was used to sign data with content type `text/plain` and the signature.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- data [data]: data that was signed
|
||||||
|
- signature [data]: the signature to verify
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- derived account [address]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_ecRecover",
|
||||||
|
"params": [
|
||||||
|
"data/plain",
|
||||||
|
"0xaabbccdd",
|
||||||
|
"0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_import
|
||||||
|
|
||||||
|
#### Import account
|
||||||
|
Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore
|
||||||
|
format.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export)
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- imported key [object]:
|
||||||
|
- key.address [address]: address of the imported key
|
||||||
|
- key.type [string]: type of the account
|
||||||
|
- key.url [string]: key URL
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_import",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "c7412fc59930fd90099c917a50e5f11d0934b2f5",
|
||||||
|
"crypto": {
|
||||||
|
"cipher": "aes-128-ctr",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "401c39a7c7af0388491c3d3ecb39f532"
|
||||||
|
},
|
||||||
|
"ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204",
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a"
|
||||||
|
},
|
||||||
|
"mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806"
|
||||||
|
},
|
||||||
|
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5",
|
||||||
|
"type": "account",
|
||||||
|
"url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_export
|
||||||
|
|
||||||
|
#### Export account from keystore
|
||||||
|
Export a private key from the keystore. The exported private key is encrypted with the original passphrase. When the
|
||||||
|
key is imported later this passphrase is required.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- account [address]: export private key that is associated with this account
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for
|
||||||
|
more information
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_export",
|
||||||
|
"params": [
|
||||||
|
"0xc7412fc59930fd90099c917a50e5f11d0934b2f5"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"address": "c7412fc59930fd90099c917a50e5f11d0934b2f5",
|
||||||
|
"crypto": {
|
||||||
|
"cipher": "aes-128-ctr",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "401c39a7c7af0388491c3d3ecb39f532"
|
||||||
|
},
|
||||||
|
"ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204",
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a"
|
||||||
|
},
|
||||||
|
"mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806"
|
||||||
|
},
|
||||||
|
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## UI API
|
||||||
|
|
||||||
|
These methods needs to be implemented by a UI listener.
|
||||||
|
|
||||||
|
By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with
|
||||||
|
denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented.
|
||||||
|
See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'.
|
||||||
|
|
||||||
|
All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key.
|
||||||
|
|
||||||
|
See the [ui api changelog](intapi_changelog.md) for information about changes to this API.
|
||||||
|
|
||||||
|
OBS! A slight deviation from `json` standard is in place: every request and response should be confined to a single line.
|
||||||
|
Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make
|
||||||
|
things simpler for both parties.
|
||||||
|
|
||||||
|
### ApproveTx
|
||||||
|
|
||||||
|
Invoked when there's a transaction for approval.
|
||||||
|
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
Here's a method invocation:
|
||||||
|
```bash
|
||||||
|
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||||
|
"to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Invalid checksum on to-address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Info",
|
||||||
|
"message": "safeSend(address: 0x0000000000000000000000000000000000000012)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "127.0.0.1:48486",
|
||||||
|
"local": "localhost:8550",
|
||||||
|
"scheme": "HTTP/1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The same method invocation, but with invalid data:
|
||||||
|
```bash
|
||||||
|
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000002000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||||
|
"to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000002000000000000000000000000000000000000000000000012",
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Invalid checksum on to-address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Transaction data did not match ABI-interface: WARNING: Supplied data is stuffed with extra data. \nWant 0000000000000002000000000000000000000000000000000000000000000012\nHave 0000000000000000000000000000000000000000000000000000000000000012\nfor method safeSend(address)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "127.0.0.1:48492",
|
||||||
|
"local": "localhost:8550",
|
||||||
|
"scheme": "HTTP/1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
One which has missing `to`, but with no `data`:
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 3,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "",
|
||||||
|
"to": null,
|
||||||
|
"gas": "0x0",
|
||||||
|
"gasPrice": "0x0",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": null,
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "CRITICAL",
|
||||||
|
"message": "Tx will create contract with empty code!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ApproveExport
|
||||||
|
|
||||||
|
Invoked when a request to export an account has been made.
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 7,
|
||||||
|
"method": "ApproveExport",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "0x0000000000000000000000000000000000000000",
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ApproveListing
|
||||||
|
|
||||||
|
Invoked when a request for account listing has been made.
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"method": "ApproveListing",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"type": "Account",
|
||||||
|
"url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42",
|
||||||
|
"address": "0x123409812340981234098123409812deadbeef42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Account",
|
||||||
|
"url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42",
|
||||||
|
"address": "0xcafebabedeadbeef34098123409812deadbeef42"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### ApproveSignData
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 4,
|
||||||
|
"method": "ApproveSignData",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "0x123409812340981234098123409812deadbeef42",
|
||||||
|
"raw_data": "0x01020304",
|
||||||
|
"message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004",
|
||||||
|
"hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310",
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ShowInfo
|
||||||
|
|
||||||
|
The UI should show the info to the user. Does not expect response.
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 9,
|
||||||
|
"method": "ShowInfo",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"text": "Tests completed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ShowError
|
||||||
|
|
||||||
|
The UI should show the info to the user. Does not expect response.
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "ShowError",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"text": "Testing 'ShowError'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### OnApproved
|
||||||
|
|
||||||
|
`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions.
|
||||||
|
|
||||||
|
When implementing rate-limited rules, this callback should be used.
|
||||||
|
|
||||||
|
TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`.
|
||||||
|
|
||||||
|
### OnSignerStartup
|
||||||
|
|
||||||
|
This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api,
|
||||||
|
in k/v-form.
|
||||||
|
|
||||||
|
Example call:
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "OnSignerStartup",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"extapi_http": "http://localhost:8550",
|
||||||
|
"extapi_ipc": null,
|
||||||
|
"extapi_version": "2.0.0",
|
||||||
|
"intapi_version": "1.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Rules for UI apis
|
||||||
|
|
||||||
|
A UI should conform to the following rules.
|
||||||
|
|
||||||
|
* A UI MUST NOT load any external resources that were not embedded/part of the UI package.
|
||||||
|
* For example, not load icons, stylesheets from the internet
|
||||||
|
* Not load files from the filesystem, unless they reside in the same local directory (e.g. config files)
|
||||||
|
* A Graphical UI MUST show the blocky-identicon for ethereum addresses.
|
||||||
|
* A UI MUST warn display approproate warning if the destination-account is formatted with invalid checksum.
|
||||||
|
* A UI MUST NOT open any ports or services
|
||||||
|
* The signer opens the public port
|
||||||
|
* A UI SHOULD verify the permissions on the signer binary, and refuse to execute or warn if permissions allow non-user write.
|
||||||
|
* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed
|
||||||
|
* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts
|
||||||
|
* The signer provides accounts
|
||||||
|
* A UI SHOULD, to the best extent possible, use static linking / bundling, so that required libraries are bundled
|
||||||
|
along with the UI.
|
||||||
|
|
||||||
|
|
||||||
|
### UI Implementations
|
||||||
|
|
||||||
|
There are a couple of implementation for a UI. We'll try to keep this list up to date.
|
||||||
|
|
||||||
|
| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters|
|
||||||
|
| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- |
|
||||||
|
| QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
||||||
|
| GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
||||||
|
| Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
||||||
|
| Clef UI| https://github.com/kyokan/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)|
|
||||||
219
cmd/clef/datatypes.md
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
## UI Client interface
|
||||||
|
|
||||||
|
These data types are defined in the channel between clef and the UI
|
||||||
|
### SignDataRequest
|
||||||
|
|
||||||
|
SignDataRequest contains information about a pending request to sign some data. The data to be signed can be of various types, defined by content-type. Clef has done most of the work in canonicalizing and making sense of the data, and it's up to the UI to presentthe user with the contents of the `message`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"content_type": "text/plain",
|
||||||
|
"address": "0xDEADbEeF000000000000000000000000DeaDbeEf",
|
||||||
|
"raw_data": "GUV0aGVyZXVtIFNpZ25lZCBNZXNzYWdlOgoxMWhlbGxvIHdvcmxk",
|
||||||
|
"message": [
|
||||||
|
{
|
||||||
|
"name": "message",
|
||||||
|
"value": "\u0019Ethereum Signed Message:\n11hello world",
|
||||||
|
"type": "text/plain"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hash": "0xd9eba16ed0ecae432b71fe008c98cc872bb4cc214d3220a36f365326cf807d68",
|
||||||
|
"meta": {
|
||||||
|
"remote": "localhost:9999",
|
||||||
|
"local": "localhost:8545",
|
||||||
|
"scheme": "http",
|
||||||
|
"User-Agent": "Firefox 3.2",
|
||||||
|
"Origin": "www.malicious.ru"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### SignDataResponse - approve
|
||||||
|
|
||||||
|
Response to SignDataRequest
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"approved": true,
|
||||||
|
"Password": "apassword"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### SignDataResponse - deny
|
||||||
|
|
||||||
|
Response to SignDataRequest
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"approved": false,
|
||||||
|
"Password": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### SignTxRequest
|
||||||
|
|
||||||
|
SignTxRequest contains information about a pending request to sign a transaction. Aside from the transaction itself, there is also a `call_info`-struct. That struct contains messages of various types, that the user should be informed of.
|
||||||
|
|
||||||
|
As in any request, it's important to consider that the `meta` info also contains untrusted data.
|
||||||
|
|
||||||
|
The `transaction` (on input into clef) can have either `data` or `input` -- if both are set, they must be identical, otherwise an error is generated. However, Clef will always use `data` when passing this struct on (if Clef does otherwise, please file a ticket)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0xDEADbEeF000000000000000000000000DeaDbeEf",
|
||||||
|
"to": null,
|
||||||
|
"gas": "0x3e8",
|
||||||
|
"gasPrice": "0x5",
|
||||||
|
"value": "0x6",
|
||||||
|
"nonce": "0x1",
|
||||||
|
"data": "0x01020304"
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "Warning",
|
||||||
|
"message": "Something looks odd, show this message as a warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Info",
|
||||||
|
"message": "User should see this aswell"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "localhost:9999",
|
||||||
|
"local": "localhost:8545",
|
||||||
|
"scheme": "http",
|
||||||
|
"User-Agent": "Firefox 3.2",
|
||||||
|
"Origin": "www.malicious.ru"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### SignDataResponse - approve
|
||||||
|
|
||||||
|
Response to SignDataRequest. This response needs to contain the `transaction`, because the UI is free to make modifications to the transaction.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0xDEADbEeF000000000000000000000000DeaDbeEf",
|
||||||
|
"to": null,
|
||||||
|
"gas": "0x3e8",
|
||||||
|
"gasPrice": "0x5",
|
||||||
|
"value": "0x6",
|
||||||
|
"nonce": "0x4",
|
||||||
|
"data": "0x04030201"
|
||||||
|
},
|
||||||
|
"approved": true,
|
||||||
|
"password": "apassword"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### SignDataResponse - deny
|
||||||
|
|
||||||
|
Response to SignDataRequest. When denying a request, there's no need to provide the transaction in return
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"approved": false,
|
||||||
|
"Password": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### OnApproved - SignTransactionResult
|
||||||
|
|
||||||
|
SignTransactionResult is used in the call `clef` -> `OnApprovedTx(result)`
|
||||||
|
|
||||||
|
This occurs _after_ successful completion of the entire signing procedure, but right before the signed transaction is passed to the external caller. This method (and data) can be used by the UI to signal to the user that the transaction was signed, but it is primarily useful for ruleset implementations.
|
||||||
|
|
||||||
|
A ruleset that implements a rate limitation needs to know what transactions are sent out to the external interface. By hooking into this methods, the ruleset can maintain track of that count.
|
||||||
|
|
||||||
|
**OBS:** Note that if an attacker can restore your `clef` data to a previous point in time (e.g through a backup), the attacker can reset such windows, even if he/she is unable to decrypt the content.
|
||||||
|
|
||||||
|
The `OnApproved` method cannot be responded to, it's purely informative
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"raw": "0xf85d640101948a8eafb1cf62bfbeb1741769dae1a9dd47996192018026a0716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293a04e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed",
|
||||||
|
"tx": {
|
||||||
|
"nonce": "0x64",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"gas": "0x1",
|
||||||
|
"to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192",
|
||||||
|
"value": "0x1",
|
||||||
|
"input": "0x",
|
||||||
|
"v": "0x26",
|
||||||
|
"r": "0x716bd90515acb1e68e5ac5867aa11a1e65399c3349d479f5fb698554ebc6f293",
|
||||||
|
"s": "0x4e8a4ebfff434e971e0ef12c5bf3a881b06fd04fc3f8b8a7291fb67a26a1d4ed",
|
||||||
|
"hash": "0x662f6d772692dd692f1b5e8baa77a9ff95bbd909362df3fc3d301aafebde5441"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### UserInputRequest
|
||||||
|
|
||||||
|
Sent when clef needs the user to provide data. If 'password' is true, the input field should be treated accordingly (echo-free)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"prompt": "The question to ask the user",
|
||||||
|
"title": "The title here",
|
||||||
|
"isPassword": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### UserInputResponse
|
||||||
|
|
||||||
|
Response to SignDataRequest
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"text": "The textual response from user"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### ListRequest
|
||||||
|
|
||||||
|
Sent when a request has been made to list addresses. The UI is provided with the full `account`s, including local directory names. Note: this information is not passed back to the external caller, who only sees the `address`es.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "0xdeadbeef000000000000000000000000deadbeef",
|
||||||
|
"url": "keystore:///path/to/keyfile/a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x1111111122222222222233333333334444444444",
|
||||||
|
"url": "keystore:///path/to/keyfile/b"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "localhost:9999",
|
||||||
|
"local": "localhost:8545",
|
||||||
|
"scheme": "http",
|
||||||
|
"User-Agent": "Firefox 3.2",
|
||||||
|
"Origin": "www.malicious.ru"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
### UserInputResponse
|
||||||
|
|
||||||
|
Response to list request. The response contains a list of all addresses to show to the caller. Note: the UI is free to respond with any address the caller, regardless of whether it exists or not
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "0x0000000000000000000000000000000000000000",
|
||||||
|
"url": ".. ignored .."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0xffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
BIN
cmd/clef/docs/clef_architecture_pt1.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
cmd/clef/docs/clef_architecture_pt2.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
cmd/clef/docs/clef_architecture_pt3.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
cmd/clef/docs/clef_architecture_pt4.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
cmd/clef/docs/qubes/clef_qubes_http.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
cmd/clef/docs/qubes/clef_qubes_qrexec.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
cmd/clef/docs/qubes/qrexec-example.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
23
cmd/clef/docs/qubes/qubes-client.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This implements a dispatcher which listens to localhost:8550, and proxies
|
||||||
|
requests via qrexec to the service qubes.EthSign on a target domain
|
||||||
|
"""
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import socketserver,subprocess
|
||||||
|
|
||||||
|
PORT=8550
|
||||||
|
TARGET_DOMAIN= 'debian-work'
|
||||||
|
|
||||||
|
class Dispatcher(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
post_data = self.rfile.read(int(self.headers['Content-Length']))
|
||||||
|
p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
output = p.communicate(post_data)[0]
|
||||||
|
self.wfile.write(output)
|
||||||
|
|
||||||
|
|
||||||
|
with socketserver.TCPServer(("",PORT), Dispatcher) as httpd:
|
||||||
|
print("Serving at port", PORT)
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||