mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
commit
d9887cfe3d
1381 changed files with 52684 additions and 2516266 deletions
|
|
@ -1,3 +1,6 @@
|
||||||
.git
|
**/.git
|
||||||
|
**/*_test.go
|
||||||
|
|
||||||
build/_workspace
|
build/_workspace
|
||||||
build/_bin
|
build/_bin
|
||||||
|
tests/testdata
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "tests"]
|
||||||
|
path = tests/testdata
|
||||||
|
url = https://github.com/ethereum/tests
|
||||||
21
.mailmap
21
.mailmap
|
|
@ -69,7 +69,7 @@ RJ Catalano <rj@erisindustries.com>
|
||||||
|
|
||||||
Nchinda Nchinda <nchinda2@gmail.com>
|
Nchinda Nchinda <nchinda2@gmail.com>
|
||||||
|
|
||||||
Aron Fischer <homotopycolimit@users.noreply.github.com>
|
Aron Fischer <github@aron.guru> <homotopycolimit@users.noreply.github.com>
|
||||||
|
|
||||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||||
|
|
||||||
|
|
@ -90,3 +90,22 @@ Nick Johnson <arachnid@notdot.net>
|
||||||
Henning Diedrich <hd@eonblast.com>
|
Henning Diedrich <hd@eonblast.com>
|
||||||
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
|
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
|
||||||
|
|
||||||
|
Felix Lange <fjl@twurst.com>
|
||||||
|
Felix Lange <fjl@twurst.com> <fjl@users.noreply.github.com>
|
||||||
|
|
||||||
|
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||||
|
|
||||||
|
Louis Holbrook <dev@holbrook.no>
|
||||||
|
Louis Holbrook <dev@holbrook.no> <nolash@users.noreply.github.com>
|
||||||
|
|
||||||
|
Thomas Bocek <tom@tomp2p.net>
|
||||||
|
|
||||||
|
Victor Tran <vu.tran54@gmail.com>
|
||||||
|
|
||||||
|
Justin Drake <drakefjustin@gmail.com>
|
||||||
|
|
||||||
|
Frank Wang <eternnoir@gmail.com>
|
||||||
|
|
||||||
|
Gary Rong <garyrong0905@gmail.com>
|
||||||
|
|
||||||
|
Guillaume Nicolas <guin56@gmail.com>
|
||||||
|
|
|
||||||
50
.travis.yml
50
.travis.yml
|
|
@ -6,7 +6,7 @@ matrix:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.7.5
|
go: 1.7.6
|
||||||
script:
|
script:
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
|
|
@ -19,7 +19,7 @@ matrix:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.8
|
go: 1.8.3
|
||||||
script:
|
script:
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
|
|
@ -29,7 +29,7 @@ matrix:
|
||||||
- go run build/ci.go test -coverage -misspell
|
- go run build/ci.go test -coverage -misspell
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: 1.8
|
go: 1.8.3
|
||||||
sudo: required
|
sudo: required
|
||||||
script:
|
script:
|
||||||
- brew update
|
- brew update
|
||||||
|
|
@ -42,7 +42,7 @@ matrix:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.8
|
go: 1.8.3
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- azure-linux
|
- azure-linux
|
||||||
|
|
@ -53,6 +53,7 @@ matrix:
|
||||||
- debhelper
|
- debhelper
|
||||||
- dput
|
- dput
|
||||||
- gcc-multilib
|
- gcc-multilib
|
||||||
|
- fakeroot
|
||||||
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 debsrc -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>" -upload ppa:ethereum/ethereum
|
||||||
|
|
@ -71,9 +72,8 @@ matrix:
|
||||||
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- GOARM=7 CC=arm-linux-gnueabihf-gcc go run build/ci.go install -arch arm
|
- GOARM=7 CC=arm-linux-gnueabihf-gcc go run build/ci.go install -arch arm
|
||||||
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
# ARM64 linux builds are broken in Go 1.8 (https://github.com/golang/go/issues/19137), reenable in Go 1.8.1
|
- CC=aarch64-linux-gnu-gcc go run build/ci.go install -arch arm64
|
||||||
# - CC=aarch64-linux-gnu-gcc go run build/ci.go install -arch arm64
|
- 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
|
- os: linux
|
||||||
|
|
@ -81,7 +81,7 @@ matrix:
|
||||||
sudo: required
|
sudo: required
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: 1.8
|
go: 1.8.3
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
script:
|
script:
|
||||||
|
|
@ -101,24 +101,6 @@ matrix:
|
||||||
- for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done
|
- for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done
|
||||||
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# This builder is a temporary fallback for building ARM64 while Go 1.8 is fixed
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
sudo: required
|
|
||||||
go: 1.7.5
|
|
||||||
env:
|
|
||||||
- azure-linux-arm64-fallback
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- gcc-multilib
|
|
||||||
script:
|
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
|
||||||
- sudo ln -s /usr/include/asm-generic /usr/include/asm
|
|
||||||
|
|
||||||
- CC=aarch64-linux-gnu-gcc go run build/ci.go install -arch arm64
|
|
||||||
- go run build/ci.go archive -arch arm64 -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
|
- os: linux
|
||||||
dist: precise # Needed for the android tools
|
dist: precise # Needed for the android tools
|
||||||
|
|
@ -139,16 +121,16 @@ matrix:
|
||||||
- azure-android
|
- azure-android
|
||||||
- maven-android
|
- maven-android
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz | tar -xz
|
- curl https://storage.googleapis.com/golang/go1.8.3.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-r13b-linux-x86_64.zip -o android-ndk-r13b.zip
|
- curl https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip -o android-ndk-r14b.zip
|
||||||
- unzip -q android-ndk-r13b.zip && rm android-ndk-r13b.zip
|
- unzip -q android-ndk-r14b.zip && rm android-ndk-r14b.zip
|
||||||
- mv android-ndk-r13b $HOME
|
- mv android-ndk-r14b $HOME
|
||||||
- export ANDROID_NDK=$HOME/android-ndk-r13b
|
- export ANDROID_NDK=$HOME/android-ndk-r14b
|
||||||
|
|
||||||
- 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
|
||||||
|
|
@ -156,7 +138,7 @@ matrix:
|
||||||
|
|
||||||
# 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
|
- os: osx
|
||||||
go: 1.8
|
go: 1.8.3
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- azure-ios
|
- azure-ios
|
||||||
|
|
@ -166,7 +148,7 @@ matrix:
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# Build the iOS framework and upload it to CocoaPods and Azure
|
# Build the iOS framework and upload it to CocoaPods and Azure
|
||||||
- gem uninstall cocoapods -a
|
- gem uninstall cocoapods -a -x
|
||||||
- gem install cocoapods
|
- gem install cocoapods
|
||||||
|
|
||||||
- mv ~/.cocoapods/repos/master ~/.cocoapods/repos/master.bak
|
- mv ~/.cocoapods/repos/master ~/.cocoapods/repos/master.bak
|
||||||
|
|
@ -182,7 +164,7 @@ matrix:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.8
|
go: 1.8.3
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
script:
|
script:
|
||||||
|
|
|
||||||
24
AUTHORS
24
AUTHORS
|
|
@ -3,24 +3,30 @@
|
||||||
Ales Katona <ales@coinbase.com>
|
Ales Katona <ales@coinbase.com>
|
||||||
Alex Leverington <alex@ethdev.com>
|
Alex Leverington <alex@ethdev.com>
|
||||||
Alexandre Van de Sande <alex.vandesande@ethdev.com>
|
Alexandre Van de Sande <alex.vandesande@ethdev.com>
|
||||||
Aron Fischer <homotopycolimit@users.noreply.github.com>
|
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>
|
||||||
|
Brian Schroeder <bts@gmail.com>
|
||||||
Casey Detrio <cdetrio@gmail.com>
|
Casey Detrio <cdetrio@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>
|
||||||
|
Diego Siqueira <DiSiqueira@users.noreply.github.com>
|
||||||
Elliot Shepherd <elliot@identitii.com>
|
Elliot Shepherd <elliot@identitii.com>
|
||||||
Enrique Fynn <enriquefynn@gmail.com>
|
Enrique Fynn <enriquefynn@gmail.com>
|
||||||
Ethan Buchman <ethan@coinculture.info>
|
Ethan Buchman <ethan@coinculture.info>
|
||||||
Fabian Vogelsteller <fabian@frozeman.de>
|
Fabian Vogelsteller <fabian@frozeman.de>
|
||||||
Fabio Berger <fabioberger1991@gmail.com>
|
Fabio Berger <fabioberger1991@gmail.com>
|
||||||
Felix Lange <fjl@twurst.com>
|
Felix Lange <fjl@twurst.com>
|
||||||
|
Frank Wang <eternnoir@gmail.com>
|
||||||
|
Gary Rong <garyrong0905@gmail.com>
|
||||||
Gregg Dourgarian <greggd@tempworks.com>
|
Gregg Dourgarian <greggd@tempworks.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>
|
||||||
Jae Kwon <jkwon.work@gmail.com>
|
Jae Kwon <jkwon.work@gmail.com>
|
||||||
|
Jamie Pitts <james.pitts@gmail.com>
|
||||||
Jason Carver <jacarver@linkedin.com>
|
Jason Carver <jacarver@linkedin.com>
|
||||||
Jeff R. Allen <jra@nella.org>
|
Jeff R. Allen <jra@nella.org>
|
||||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||||
|
|
@ -28,15 +34,20 @@ Jens Agerberg <github@agerberg.me>
|
||||||
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>
|
||||||
Kenji Siu <kenji@isuntv.com>
|
Kenji Siu <kenji@isuntv.com>
|
||||||
Kobi Gurkan <kobigurk@gmail.com>
|
Kobi Gurkan <kobigurk@gmail.com>
|
||||||
Lefteris Karapetsas <lefteris@refu.co>
|
Lefteris Karapetsas <lefteris@refu.co>
|
||||||
Leif Jurvetson <leijurv@gmail.com>
|
Leif Jurvetson <leijurv@gmail.com>
|
||||||
|
Lewis Marshall <lewis@lmars.net>
|
||||||
|
Louis Holbrook <dev@holbrook.no>
|
||||||
|
Luca Zeug <luclu@users.noreply.github.com>
|
||||||
Maran Hidskes <maran.hidskes@gmail.com>
|
Maran Hidskes <maran.hidskes@gmail.com>
|
||||||
Marek Kotewicz <marek.kotewicz@gmail.com>
|
Marek Kotewicz <marek.kotewicz@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>
|
||||||
|
Micah Zoltu <micah@zoltu.net>
|
||||||
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>
|
||||||
|
|
@ -47,17 +58,28 @@ RJ Catalano <rj@erisindustries.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>
|
||||||
Rémy Roy <remyroy@remyroy.com>
|
Rémy Roy <remyroy@remyroy.com>
|
||||||
|
Shintaro Kaneko <kaneshin0120@gmail.com>
|
||||||
Stein Dekker <dekker.stein@gmail.com>
|
Stein Dekker <dekker.stein@gmail.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>
|
||||||
Tosh Camille <tochecamille@gmail.com>
|
Tosh Camille <tochecamille@gmail.com>
|
||||||
|
Valentin Wüstholz <wuestholz@users.noreply.github.com>
|
||||||
|
Victor Farazdagi <simple.square@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>
|
||||||
|
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>
|
||||||
|
Zahoor Mohamed <zahoor@zahoor.in>
|
||||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||||
|
holisticode <holistic.computing@gmail.com>
|
||||||
|
ken10100147 <sunhongping@kanjian.com>
|
||||||
|
ligi <ligi@ligi.de>
|
||||||
|
xiekeyang <xiekeyang@users.noreply.github.com>
|
||||||
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||||
|
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ ADD . /go-ethereum
|
||||||
RUN \
|
RUN \
|
||||||
apk add --update git go make gcc musl-dev linux-headers && \
|
apk add --update git go make gcc musl-dev linux-headers && \
|
||||||
(cd go-ethereum && make geth) && \
|
(cd go-ethereum && make geth) && \
|
||||||
cp go-ethereum/build/bin/geth /geth && \
|
cp go-ethereum/build/bin/geth /usr/local/bin/ && \
|
||||||
apk del git go make gcc musl-dev linux-headers && \
|
apk del git go make gcc musl-dev linux-headers && \
|
||||||
rm -rf /go-ethereum && rm -rf /var/cache/apk/*
|
rm -rf /go-ethereum && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
EXPOSE 8545
|
EXPOSE 8545
|
||||||
EXPOSE 30303
|
EXPOSE 30303
|
||||||
|
EXPOSE 30303/udp
|
||||||
|
|
||||||
ENTRYPOINT ["/geth"]
|
ENTRYPOINT ["geth"]
|
||||||
|
|
|
||||||
7
Makefile
7
Makefile
|
|
@ -2,7 +2,7 @@
|
||||||
# with Go source code. If you know what GOPATH is then you probably
|
# with Go source code. If you know what GOPATH is then you probably
|
||||||
# don't need to bother with make.
|
# don't need to bother with make.
|
||||||
|
|
||||||
.PHONY: geth android ios geth-cross evm all test clean
|
.PHONY: geth android ios geth-cross swarm evm all test clean
|
||||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
|
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
|
||||||
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||||
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
|
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
|
||||||
|
|
@ -16,6 +16,11 @@ geth:
|
||||||
@echo "Done building."
|
@echo "Done building."
|
||||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||||
|
|
||||||
|
swarm:
|
||||||
|
build/env.sh go run build/ci.go install ./cmd/swarm
|
||||||
|
@echo "Done building."
|
||||||
|
@echo "Run \"$(GOBIN)/swarm\" to launch swarm."
|
||||||
|
|
||||||
evm:
|
evm:
|
||||||
build/env.sh go run build/ci.go install ./cmd/evm
|
build/env.sh go run build/ci.go install ./cmd/evm
|
||||||
@echo "Done building."
|
@echo "Done building."
|
||||||
|
|
|
||||||
43
README.md
43
README.md
|
|
@ -1,4 +1,4 @@
|
||||||
## Ethereum Go
|
## Go Ethereum
|
||||||
|
|
||||||
Official golang implementation of the Ethereum protocol.
|
Official golang implementation of the Ethereum protocol.
|
||||||
|
|
||||||
|
|
@ -32,10 +32,9 @@ 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. |
|
||||||
| `disasm` | Bytecode disassembler to convert EVM (Ethereum Virtual Machine) bytecode into more user friendly assembly-like opcodes (e.g. `echo "6001" | disasm`). For details on the individual opcodes, please see pages 22-30 of the [Ethereum Yellow Paper](http://gavwood.com/paper.pdf). |
|
|
||||||
| `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 insolated, 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 insolated, 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`). |
|
||||||
|
|
@ -70,7 +69,7 @@ This command will:
|
||||||
(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 too 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
|
### Full node on the Ethereum test network
|
||||||
|
|
||||||
|
|
@ -84,21 +83,39 @@ $ geth --testnet --fast --cache=512 console
|
||||||
```
|
```
|
||||||
|
|
||||||
The `--fast`, `--cache` flags and `console` subcommand have the exact same meaning as above and they
|
The `--fast`, `--cache` flags and `console` subcommand have the exact same meaning as above and they
|
||||||
are equially useful on the testnet too. Please see above for their explanations if you've skipped to
|
are equally useful on the testnet too. Please see above for their explanations if you've skipped to
|
||||||
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).
|
itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on Linux). Note, on OSX
|
||||||
|
and Linux this also means that attaching to a running testnet node requires the use of a custom
|
||||||
|
endpoint since `geth attach` will try to attach to a production node endpoint by default. E.g.
|
||||||
|
`geth attach <datadir>/testnet/geth.ipc`. Windows users are not affected by this.
|
||||||
* Instead of connecting the main Ethereum network, the client will connect to the test network,
|
* Instead of connecting the main Ethereum network, the client will connect to the test network,
|
||||||
which uses different P2P bootnodes, different network IDs and genesis states.
|
which uses different P2P bootnodes, different network IDs and genesis states.
|
||||||
|
|
||||||
*Note: Although there are some internal protective measures to prevent transactions from crossing
|
*Note: Although there are some internal protective measures to prevent transactions from crossing
|
||||||
over between the main network and test network (different starting nonces), you should make sure to
|
over between the main network and test network, you should make sure to always use separate accounts
|
||||||
always use separate accounts for play-money and real-money. Unless you manually move accounts, Geth
|
for play-money and real-money. Unless you manually move accounts, Geth will by default correctly
|
||||||
will by default correctly separate the two networks and will not make any accounts available between
|
separate the two networks and will not make any accounts available between them.*
|
||||||
them.*
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a configuration file via:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ geth --config /path/to/your_config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
To get an idea how the file should look like you can use the `dumpconfig` subcommand to export your existing configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ geth --your-favourite-flags dumpconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
*Note: This works only with geth v1.6.0 and above*
|
||||||
|
|
||||||
#### Docker quick start
|
#### Docker quick start
|
||||||
|
|
||||||
|
|
@ -161,6 +178,12 @@ and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"config": {
|
||||||
|
"chainId": 0,
|
||||||
|
"homesteadBlock": 0,
|
||||||
|
"eip155Block": 0,
|
||||||
|
"eip158Block": 0
|
||||||
|
},
|
||||||
"alloc" : {},
|
"alloc" : {},
|
||||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||||
"difficulty" : "0x20000",
|
"difficulty" : "0x20000",
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.6.0
|
1.7.0
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,9 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -67,7 +65,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
}
|
}
|
||||||
method = m
|
method = m
|
||||||
}
|
}
|
||||||
arguments, err := method.pack(method, args...)
|
arguments, err := method.pack(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -78,199 +76,6 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
return append(method.Id(), arguments...), nil
|
return append(method.Id(), arguments...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// toGoSliceType parses the input and casts it to the proper slice defined by the ABI
|
|
||||||
// argument in T.
|
|
||||||
func toGoSlice(i int, t Argument, output []byte) (interface{}, error) {
|
|
||||||
index := i * 32
|
|
||||||
// The slice must, at very least be large enough for the index+32 which is exactly the size required
|
|
||||||
// for the [offset in output, size of offset].
|
|
||||||
if index+32 > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32)
|
|
||||||
}
|
|
||||||
elem := t.Type.Elem
|
|
||||||
|
|
||||||
// first we need to create a slice of the type
|
|
||||||
var refSlice reflect.Value
|
|
||||||
switch elem.T {
|
|
||||||
case IntTy, UintTy, BoolTy:
|
|
||||||
// create a new reference slice matching the element type
|
|
||||||
switch t.Type.Kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
refSlice = reflect.ValueOf([]bool(nil))
|
|
||||||
case reflect.Uint8:
|
|
||||||
refSlice = reflect.ValueOf([]uint8(nil))
|
|
||||||
case reflect.Uint16:
|
|
||||||
refSlice = reflect.ValueOf([]uint16(nil))
|
|
||||||
case reflect.Uint32:
|
|
||||||
refSlice = reflect.ValueOf([]uint32(nil))
|
|
||||||
case reflect.Uint64:
|
|
||||||
refSlice = reflect.ValueOf([]uint64(nil))
|
|
||||||
case reflect.Int8:
|
|
||||||
refSlice = reflect.ValueOf([]int8(nil))
|
|
||||||
case reflect.Int16:
|
|
||||||
refSlice = reflect.ValueOf([]int16(nil))
|
|
||||||
case reflect.Int32:
|
|
||||||
refSlice = reflect.ValueOf([]int32(nil))
|
|
||||||
case reflect.Int64:
|
|
||||||
refSlice = reflect.ValueOf([]int64(nil))
|
|
||||||
default:
|
|
||||||
refSlice = reflect.ValueOf([]*big.Int(nil))
|
|
||||||
}
|
|
||||||
case AddressTy: // address must be of slice Address
|
|
||||||
refSlice = reflect.ValueOf([]common.Address(nil))
|
|
||||||
case HashTy: // hash must be of slice hash
|
|
||||||
refSlice = reflect.ValueOf([]common.Hash(nil))
|
|
||||||
case FixedBytesTy:
|
|
||||||
refSlice = reflect.ValueOf([][]byte(nil))
|
|
||||||
default: // no other types are supported
|
|
||||||
return nil, fmt.Errorf("abi: unsupported slice type %v", elem.T)
|
|
||||||
}
|
|
||||||
|
|
||||||
var slice []byte
|
|
||||||
var size int
|
|
||||||
var offset int
|
|
||||||
if t.Type.IsSlice {
|
|
||||||
// get the offset which determines the start of this array ...
|
|
||||||
offset = int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
|
||||||
if offset+32 > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32)
|
|
||||||
}
|
|
||||||
|
|
||||||
slice = output[offset:]
|
|
||||||
// ... starting with the size of the array in elements ...
|
|
||||||
size = int(binary.BigEndian.Uint64(slice[24:32]))
|
|
||||||
slice = slice[32:]
|
|
||||||
// ... and make sure that we've at the very least the amount of bytes
|
|
||||||
// available in the buffer.
|
|
||||||
if size*32 > len(slice) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), offset+32+size*32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reslice to match the required size
|
|
||||||
slice = slice[:size*32]
|
|
||||||
} else if t.Type.IsArray {
|
|
||||||
//get the number of elements in the array
|
|
||||||
size = t.Type.SliceSize
|
|
||||||
|
|
||||||
//check to make sure array size matches up
|
|
||||||
if index+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), index+32*size)
|
|
||||||
}
|
|
||||||
//slice is there for a fixed amount of times
|
|
||||||
slice = output[index : index+size*32]
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
|
||||||
var (
|
|
||||||
inter interface{} // interface type
|
|
||||||
returnOutput = slice[i*32 : i*32+32] // the return output
|
|
||||||
)
|
|
||||||
// set inter to the correct type (cast)
|
|
||||||
switch elem.T {
|
|
||||||
case IntTy, UintTy:
|
|
||||||
inter = readInteger(t.Type.Kind, returnOutput)
|
|
||||||
case BoolTy:
|
|
||||||
inter = !allZero(returnOutput)
|
|
||||||
case AddressTy:
|
|
||||||
inter = common.BytesToAddress(returnOutput)
|
|
||||||
case HashTy:
|
|
||||||
inter = common.BytesToHash(returnOutput)
|
|
||||||
case FixedBytesTy:
|
|
||||||
inter = returnOutput
|
|
||||||
}
|
|
||||||
// append the item to our reflect slice
|
|
||||||
refSlice = reflect.Append(refSlice, reflect.ValueOf(inter))
|
|
||||||
}
|
|
||||||
|
|
||||||
// return the interface
|
|
||||||
return refSlice.Interface(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
|
||||||
switch kind {
|
|
||||||
case reflect.Uint8:
|
|
||||||
return uint8(b[len(b)-1])
|
|
||||||
case reflect.Uint16:
|
|
||||||
return binary.BigEndian.Uint16(b[len(b)-2:])
|
|
||||||
case reflect.Uint32:
|
|
||||||
return binary.BigEndian.Uint32(b[len(b)-4:])
|
|
||||||
case reflect.Uint64:
|
|
||||||
return binary.BigEndian.Uint64(b[len(b)-8:])
|
|
||||||
case reflect.Int8:
|
|
||||||
return int8(b[len(b)-1])
|
|
||||||
case reflect.Int16:
|
|
||||||
return int16(binary.BigEndian.Uint16(b[len(b)-2:]))
|
|
||||||
case reflect.Int32:
|
|
||||||
return int32(binary.BigEndian.Uint32(b[len(b)-4:]))
|
|
||||||
case reflect.Int64:
|
|
||||||
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
|
||||||
default:
|
|
||||||
return new(big.Int).SetBytes(b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func allZero(b []byte) bool {
|
|
||||||
for _, byte := range b {
|
|
||||||
if byte != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// toGoType parses the input and casts it to the proper type defined by the ABI
|
|
||||||
// argument in T.
|
|
||||||
func toGoType(i int, t Argument, output []byte) (interface{}, error) {
|
|
||||||
// we need to treat slices differently
|
|
||||||
if (t.Type.IsSlice || t.Type.IsArray) && t.Type.T != BytesTy && t.Type.T != StringTy && t.Type.T != FixedBytesTy && t.Type.T != FunctionTy {
|
|
||||||
return toGoSlice(i, t, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
index := i * 32
|
|
||||||
if index+32 > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the given index output and check whether we need to read
|
|
||||||
// a different offset and length based on the type (i.e. string, bytes)
|
|
||||||
var returnOutput []byte
|
|
||||||
switch t.Type.T {
|
|
||||||
case StringTy, BytesTy: // variable arrays are written at the end of the return bytes
|
|
||||||
// parse offset from which we should start reading
|
|
||||||
offset := int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
|
||||||
if offset+32 > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32)
|
|
||||||
}
|
|
||||||
// parse the size up until we should be reading
|
|
||||||
size := int(binary.BigEndian.Uint64(output[offset+24 : offset+32]))
|
|
||||||
if offset+32+size > len(output) {
|
|
||||||
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the bytes for this return value
|
|
||||||
returnOutput = output[offset+32 : offset+32+size]
|
|
||||||
default:
|
|
||||||
returnOutput = output[index : index+32]
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert the bytes to whatever is specified by the ABI.
|
|
||||||
switch t.Type.T {
|
|
||||||
case IntTy, UintTy:
|
|
||||||
return readInteger(t.Type.Kind, returnOutput), nil
|
|
||||||
case BoolTy:
|
|
||||||
return !allZero(returnOutput), nil
|
|
||||||
case AddressTy:
|
|
||||||
return common.BytesToAddress(returnOutput), nil
|
|
||||||
case HashTy:
|
|
||||||
return common.BytesToHash(returnOutput), nil
|
|
||||||
case BytesTy, FixedBytesTy, FunctionTy:
|
|
||||||
return returnOutput, nil
|
|
||||||
case StringTy:
|
|
||||||
return string(returnOutput), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("abi: unknown type %v", t.Type.T)
|
|
||||||
}
|
|
||||||
|
|
||||||
// these variable are used to determine certain types during type assertion for
|
// these variable are used to determine certain types during type assertion for
|
||||||
// assignment.
|
// assignment.
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -48,412 +48,6 @@ func pad(input []byte, size int, left bool) []byte {
|
||||||
return common.RightPadBytes(input, size)
|
return common.RightPadBytes(input, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTypeCheck(t *testing.T) {
|
|
||||||
for i, test := range []struct {
|
|
||||||
typ string
|
|
||||||
input interface{}
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{"uint", big.NewInt(1), ""},
|
|
||||||
{"int", big.NewInt(1), ""},
|
|
||||||
{"uint30", big.NewInt(1), ""},
|
|
||||||
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
|
||||||
{"uint16", uint16(1), ""},
|
|
||||||
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
|
||||||
{"uint16[]", []uint16{1, 2, 3}, ""},
|
|
||||||
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
|
||||||
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type []uint16 as argument"},
|
|
||||||
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
|
||||||
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
|
||||||
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
|
||||||
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
|
||||||
{"address[]", []common.Address{{1}}, ""},
|
|
||||||
{"address[1]", []common.Address{{1}}, ""},
|
|
||||||
{"address[1]", [1]common.Address{{1}}, ""},
|
|
||||||
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
|
||||||
{"bytes32", [32]byte{}, ""},
|
|
||||||
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
|
||||||
{"bytes32", common.Hash{1}, ""},
|
|
||||||
{"bytes31", [31]byte{}, ""},
|
|
||||||
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
|
||||||
{"bytes", []byte{0, 1}, ""},
|
|
||||||
{"bytes", [2]byte{0, 1}, ""},
|
|
||||||
{"bytes", common.Hash{1}, ""},
|
|
||||||
{"string", "hello world", ""},
|
|
||||||
{"bytes32[]", [][32]byte{{}}, ""},
|
|
||||||
{"function", [24]byte{}, ""},
|
|
||||||
} {
|
|
||||||
typ, err := NewType(test.typ)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = typeCheck(typ, reflect.ValueOf(test.input))
|
|
||||||
if err != nil && len(test.err) == 0 {
|
|
||||||
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err == nil && len(test.err) != 0 {
|
|
||||||
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
|
||||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimpleMethodUnpack(t *testing.T) {
|
|
||||||
for i, test := range []struct {
|
|
||||||
def string // definition of the **output** ABI params
|
|
||||||
marshalledOutput []byte // evm return data
|
|
||||||
expectedOut interface{} // the expected output
|
|
||||||
outVar string // the output variable (e.g. uint32, *big.Int, etc)
|
|
||||||
err string // empty or error if expected
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
`[ { "type": "uint32" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
uint32(1),
|
|
||||||
"uint32",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint32" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
nil,
|
|
||||||
"uint16",
|
|
||||||
"abi: cannot unmarshal uint32 in to uint16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint17" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
nil,
|
|
||||||
"uint16",
|
|
||||||
"abi: cannot unmarshal *big.Int in to uint16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "uint17" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
big.NewInt(1),
|
|
||||||
"*big.Int",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
`[ { "type": "int32" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
int32(1),
|
|
||||||
"int32",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "int32" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
nil,
|
|
||||||
"int16",
|
|
||||||
"abi: cannot unmarshal int32 in to int16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "int17" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
nil,
|
|
||||||
"int16",
|
|
||||||
"abi: cannot unmarshal *big.Int in to int16",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "int17" } ]`,
|
|
||||||
pad([]byte{1}, 32, true),
|
|
||||||
big.NewInt(1),
|
|
||||||
"*big.Int",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
`[ { "type": "address" } ]`,
|
|
||||||
pad(pad([]byte{1}, 20, false), 32, true),
|
|
||||||
common.Address{1},
|
|
||||||
"address",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "bytes32" } ]`,
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
"bytes",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "bytes32" } ]`,
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
"hash",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "bytes32" } ]`,
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
"interface",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`[ { "type": "function" } ]`,
|
|
||||||
pad([]byte{1}, 32, false),
|
|
||||||
[24]byte{1},
|
|
||||||
"function",
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
|
||||||
abi, err := JSON(strings.NewReader(abiDefinition))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%d failed. %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var outvar interface{}
|
|
||||||
switch test.outVar {
|
|
||||||
case "uint8":
|
|
||||||
var v uint8
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint16":
|
|
||||||
var v uint16
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint32":
|
|
||||||
var v uint32
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "uint64":
|
|
||||||
var v uint64
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int8":
|
|
||||||
var v int8
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int16":
|
|
||||||
var v int16
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int32":
|
|
||||||
var v int32
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "int64":
|
|
||||||
var v int64
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "*big.Int":
|
|
||||||
var v *big.Int
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "address":
|
|
||||||
var v common.Address
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "bytes":
|
|
||||||
var v []byte
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "hash":
|
|
||||||
var v common.Hash
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "function":
|
|
||||||
var v [24]byte
|
|
||||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
|
||||||
outvar = v
|
|
||||||
case "interface":
|
|
||||||
err = abi.Unpack(&outvar, "method", test.marshalledOutput)
|
|
||||||
default:
|
|
||||||
t.Errorf("unsupported type '%v' please add it to the switch statement in this test", test.outVar)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && len(test.err) == 0 {
|
|
||||||
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err == nil && len(test.err) != 0 {
|
|
||||||
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
|
||||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// bit of an ugly hack for hash type but I don't feel like finding a proper solution
|
|
||||||
if test.outVar == "hash" {
|
|
||||||
tmp := outvar.(common.Hash) // without assignment it's unaddressable
|
|
||||||
outvar = tmp[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(test.expectedOut, outvar) {
|
|
||||||
t.Errorf("%d failed. Output error: expected %v, got %v", i, test.expectedOut, outvar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnpackSetInterfaceSlice(t *testing.T) {
|
|
||||||
var (
|
|
||||||
var1 = new(uint8)
|
|
||||||
var2 = new(uint8)
|
|
||||||
)
|
|
||||||
out := []interface{}{var1, var2}
|
|
||||||
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint8"}, {"type":"uint8"}]}]`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
marshalledReturn := append(pad([]byte{1}, 32, true), pad([]byte{2}, 32, true)...)
|
|
||||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if *var1 != 1 {
|
|
||||||
t.Error("expected var1 to be 1, got", *var1)
|
|
||||||
}
|
|
||||||
if *var2 != 2 {
|
|
||||||
t.Error("expected var2 to be 2, got", *var2)
|
|
||||||
}
|
|
||||||
|
|
||||||
out = []interface{}{var1}
|
|
||||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
|
||||||
|
|
||||||
expErr := "abi: cannot marshal in to slices of unequal size (require: 2, got: 1)"
|
|
||||||
if err == nil || err.Error() != expErr {
|
|
||||||
t.Error("expected err:", expErr, "Got:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnpackSetInterfaceArrayOutput(t *testing.T) {
|
|
||||||
var (
|
|
||||||
var1 = new([1]uint32)
|
|
||||||
var2 = new([1]uint32)
|
|
||||||
)
|
|
||||||
out := []interface{}{var1, var2}
|
|
||||||
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint32[1]"}, {"type":"uint32[1]"}]}]`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
marshalledReturn := append(pad([]byte{1}, 32, true), pad([]byte{2}, 32, true)...)
|
|
||||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *var1 != [1]uint32{1} {
|
|
||||||
t.Error("expected var1 to be [1], got", *var1)
|
|
||||||
}
|
|
||||||
if *var2 != [1]uint32{2} {
|
|
||||||
t.Error("expected var2 to be [2], got", *var2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPack(t *testing.T) {
|
|
||||||
for i, test := range []struct {
|
|
||||||
typ string
|
|
||||||
|
|
||||||
input interface{}
|
|
||||||
output []byte
|
|
||||||
}{
|
|
||||||
{"uint16", uint16(2), pad([]byte{2}, 32, true)},
|
|
||||||
{"uint16[]", []uint16{1, 2}, formatSliceOutput([]byte{1}, []byte{2})},
|
|
||||||
{"bytes20", [20]byte{1}, pad([]byte{1}, 32, false)},
|
|
||||||
{"uint256[]", []*big.Int{big.NewInt(1), big.NewInt(2)}, formatSliceOutput([]byte{1}, []byte{2})},
|
|
||||||
{"address[]", []common.Address{{1}, {2}}, formatSliceOutput(pad([]byte{1}, 20, false), pad([]byte{2}, 20, false))},
|
|
||||||
{"bytes32[]", []common.Hash{{1}, {2}}, formatSliceOutput(pad([]byte{1}, 32, false), pad([]byte{2}, 32, false))},
|
|
||||||
{"function", [24]byte{1}, pad([]byte{1}, 32, false)},
|
|
||||||
} {
|
|
||||||
typ, err := NewType(test.typ)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("unexpected parse error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output, err := typ.pack(reflect.ValueOf(test.input))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("unexpected pack error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(output, test.output) {
|
|
||||||
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMethodPack(t *testing.T) {
|
|
||||||
abi, err := JSON(strings.NewReader(jsondata2))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := abi.Methods["slice"].Id()
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
|
||||||
|
|
||||||
packed, err := abi.Pack("slice", []uint32{1, 2})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(packed, sig) {
|
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addrA, addrB = common.Address{1}, common.Address{2}
|
|
||||||
sig = abi.Methods["sliceAddress"].Id()
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
|
||||||
|
|
||||||
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(packed, sig) {
|
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addrC, addrD = common.Address{3}, common.Address{4}
|
|
||||||
sig = abi.Methods["sliceMultiAddress"].Id()
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
|
||||||
|
|
||||||
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(packed, sig) {
|
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
|
||||||
}
|
|
||||||
|
|
||||||
sig = abi.Methods["slice256"].Id()
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
|
||||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
|
||||||
|
|
||||||
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(packed, sig) {
|
|
||||||
t.Errorf("expected %x got %x", sig, packed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsondata = `
|
const jsondata = `
|
||||||
[
|
[
|
||||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
{ "type" : "function", "name" : "balance", "constant" : true },
|
||||||
|
|
@ -843,399 +437,3 @@ func TestBareEvents(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithStruct(t *testing.T) {
|
|
||||||
const definition = `[
|
|
||||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// using buff to make the code readable
|
|
||||||
buff := new(bytes.Buffer)
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
|
||||||
stringOut := "hello"
|
|
||||||
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
|
||||||
|
|
||||||
var inter struct {
|
|
||||||
Int *big.Int
|
|
||||||
String string
|
|
||||||
}
|
|
||||||
err = abi.Unpack(&inter, "multi", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inter.Int == nil || inter.Int.Cmp(big.NewInt(1)) != 0 {
|
|
||||||
t.Error("expected Int to be 1 got", inter.Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inter.String != stringOut {
|
|
||||||
t.Error("expected String to be", stringOut, "got", inter.String)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reversed struct {
|
|
||||||
String string
|
|
||||||
Int *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
err = abi.Unpack(&reversed, "multi", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reversed.Int == nil || reversed.Int.Cmp(big.NewInt(1)) != 0 {
|
|
||||||
t.Error("expected Int to be 1 got", reversed.Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
if reversed.String != stringOut {
|
|
||||||
t.Error("expected String to be", stringOut, "got", reversed.String)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiReturnWithSlice(t *testing.T) {
|
|
||||||
const definition = `[
|
|
||||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// using buff to make the code readable
|
|
||||||
buff := new(bytes.Buffer)
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
|
||||||
stringOut := "hello"
|
|
||||||
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
|
||||||
|
|
||||||
var inter []interface{}
|
|
||||||
err = abi.Unpack(&inter, "multi", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(inter) != 2 {
|
|
||||||
t.Fatal("expected 2 results got", len(inter))
|
|
||||||
}
|
|
||||||
|
|
||||||
if num, ok := inter[0].(*big.Int); !ok || num.Cmp(big.NewInt(1)) != 0 {
|
|
||||||
t.Error("expected index 0 to be 1 got", num)
|
|
||||||
}
|
|
||||||
|
|
||||||
if str, ok := inter[1].(string); !ok || str != stringOut {
|
|
||||||
t.Error("expected index 1 to be", stringOut, "got", str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarshalArrays(t *testing.T) {
|
|
||||||
const definition = `[
|
|
||||||
{ "name" : "bytes32", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
|
||||||
{ "name" : "bytes10", "constant" : false, "outputs": [ { "type": "bytes10" } ] }
|
|
||||||
]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
output := common.LeftPadBytes([]byte{1}, 32)
|
|
||||||
|
|
||||||
var bytes10 [10]byte
|
|
||||||
err = abi.Unpack(&bytes10, "bytes32", output)
|
|
||||||
if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
|
|
||||||
t.Error("expected error or bytes32 not be assignable to bytes10:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes32 [32]byte
|
|
||||||
err = abi.Unpack(&bytes32, "bytes32", output)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(bytes32[:], output) {
|
|
||||||
t.Error("expected bytes32[31] to be 1 got", bytes32[31])
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
B10 [10]byte
|
|
||||||
B32 [32]byte
|
|
||||||
)
|
|
||||||
|
|
||||||
var b10 B10
|
|
||||||
err = abi.Unpack(&b10, "bytes32", output)
|
|
||||||
if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
|
|
||||||
t.Error("expected error or bytes32 not be assignable to bytes10:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var b32 B32
|
|
||||||
err = abi.Unpack(&b32, "bytes32", output)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(b32[:], output) {
|
|
||||||
t.Error("expected bytes32[31] to be 1 got", bytes32[31])
|
|
||||||
}
|
|
||||||
|
|
||||||
output[10] = 1
|
|
||||||
var shortAssignLong [32]byte
|
|
||||||
err = abi.Unpack(&shortAssignLong, "bytes10", output)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(output, shortAssignLong[:]) {
|
|
||||||
t.Errorf("expected %x to be %x", shortAssignLong, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
|
||||||
const definition = `[
|
|
||||||
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
|
||||||
{ "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] },
|
|
||||||
{ "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] },
|
|
||||||
{ "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
|
||||||
{ "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
|
|
||||||
{ "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] },
|
|
||||||
{ "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] },
|
|
||||||
{ "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
|
|
||||||
{ "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
|
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
buff := new(bytes.Buffer)
|
|
||||||
|
|
||||||
// marshal int
|
|
||||||
var Int *big.Int
|
|
||||||
err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if Int == nil || Int.Cmp(big.NewInt(1)) != 0 {
|
|
||||||
t.Error("expected Int to be 1 got", Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal bool
|
|
||||||
var Bool bool
|
|
||||||
err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !Bool {
|
|
||||||
t.Error("expected Bool to be true")
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal dynamic bytes max length 32
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
bytesOut := common.RightPadBytes([]byte("hello"), 32)
|
|
||||||
buff.Write(bytesOut)
|
|
||||||
|
|
||||||
var Bytes []byte
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(Bytes, bytesOut) {
|
|
||||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshall dynamic bytes max length 64
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
|
||||||
bytesOut = common.RightPadBytes([]byte("hello"), 64)
|
|
||||||
buff.Write(bytesOut)
|
|
||||||
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(Bytes, bytesOut) {
|
|
||||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshall dynamic bytes max length 63
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f"))
|
|
||||||
bytesOut = common.RightPadBytes([]byte("hello"), 63)
|
|
||||||
buff.Write(bytesOut)
|
|
||||||
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(Bytes, bytesOut) {
|
|
||||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal dynamic bytes output empty
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", nil)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal dynamic bytes length 5
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
|
||||||
buff.Write(common.RightPadBytes([]byte("hello"), 32))
|
|
||||||
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(Bytes, []byte("hello")) {
|
|
||||||
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal dynamic bytes length 5
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.RightPadBytes([]byte("hello"), 32))
|
|
||||||
|
|
||||||
var hash common.Hash
|
|
||||||
err = abi.Unpack(&hash, "fixed", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32))
|
|
||||||
if hash != helloHash {
|
|
||||||
t.Errorf("Expected %x to equal %x", hash, helloHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal error
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = abi.Unpack(&Bytes, "multi", make([]byte, 64))
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expected error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal mixed bytes
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
|
||||||
fixed := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")
|
|
||||||
buff.Write(fixed)
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
|
||||||
bytesOut = common.RightPadBytes([]byte("hello"), 32)
|
|
||||||
buff.Write(bytesOut)
|
|
||||||
|
|
||||||
var out []interface{}
|
|
||||||
err = abi.Unpack(&out, "mixedBytes", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(bytesOut, out[0].([]byte)) {
|
|
||||||
t.Errorf("expected %x, got %x", bytesOut, out[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(fixed, out[1].([]byte)) {
|
|
||||||
t.Errorf("expected %x, got %x", fixed, out[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003"))
|
|
||||||
// marshal int array
|
|
||||||
var intArray [3]*big.Int
|
|
||||||
err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
var testAgainstIntArray [3]*big.Int
|
|
||||||
testAgainstIntArray[0] = big.NewInt(1)
|
|
||||||
testAgainstIntArray[1] = big.NewInt(2)
|
|
||||||
testAgainstIntArray[2] = big.NewInt(3)
|
|
||||||
|
|
||||||
for i, Int := range intArray {
|
|
||||||
if Int.Cmp(testAgainstIntArray[i]) != 0 {
|
|
||||||
t.Errorf("expected %v, got %v", testAgainstIntArray[i], Int)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// marshal address slice
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"))
|
|
||||||
|
|
||||||
var outAddr []common.Address
|
|
||||||
err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outAddr) != 1 {
|
|
||||||
t.Fatal("expected 1 item, got", len(outAddr))
|
|
||||||
}
|
|
||||||
|
|
||||||
if outAddr[0] != (common.Address{1}) {
|
|
||||||
t.Errorf("expected %x, got %x", common.Address{1}, outAddr[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal multiple address slice
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // offset
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // offset
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // size
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000200000000000000000000000000000000000000"))
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000300000000000000000000000000000000000000"))
|
|
||||||
|
|
||||||
var outAddrStruct struct {
|
|
||||||
A []common.Address
|
|
||||||
B []common.Address
|
|
||||||
}
|
|
||||||
err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("didn't expect error:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outAddrStruct.A) != 1 {
|
|
||||||
t.Fatal("expected 1 item, got", len(outAddrStruct.A))
|
|
||||||
}
|
|
||||||
|
|
||||||
if outAddrStruct.A[0] != (common.Address{1}) {
|
|
||||||
t.Errorf("expected %x, got %x", common.Address{1}, outAddrStruct.A[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(outAddrStruct.B) != 2 {
|
|
||||||
t.Fatal("expected 1 item, got", len(outAddrStruct.B))
|
|
||||||
}
|
|
||||||
|
|
||||||
if outAddrStruct.B[0] != (common.Address{2}) {
|
|
||||||
t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0])
|
|
||||||
}
|
|
||||||
if outAddrStruct.B[1] != (common.Address{3}) {
|
|
||||||
t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal invalid address slice
|
|
||||||
buff.Reset()
|
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100"))
|
|
||||||
|
|
||||||
err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes())
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ func (b *SimulatedBackend) Rollback() {
|
||||||
func (b *SimulatedBackend) rollback() {
|
func (b *SimulatedBackend) rollback() {
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.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.
|
||||||
|
|
@ -144,7 +144,8 @@ 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) {
|
||||||
return core.GetReceipt(b.database, txHash), nil
|
receipt, _, _, _ := core.GetReceipt(b.database, txHash)
|
||||||
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingCodeAt returns the code associated with an account in the pending state.
|
// PendingCodeAt returns the code associated with an account in the pending state.
|
||||||
|
|
@ -248,7 +249,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
|
||||||
// Execute the call.
|
// Execute the call.
|
||||||
msg := callmsg{call}
|
msg := callmsg{call}
|
||||||
|
|
||||||
evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain)
|
evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil)
|
||||||
// Create a new environment which holds all relevant information
|
// Create a new environment which holds all relevant information
|
||||||
// about the transaction and calling mechanisms.
|
// about the transaction and calling mechanisms.
|
||||||
vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{})
|
vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{})
|
||||||
|
|
@ -279,7 +280,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
})
|
})
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ var tmplSource = map[Lang]string{
|
||||||
// tmplSourceGo is the Go source template use to generate the contract binding
|
// tmplSourceGo is the Go source template use to generate the contract binding
|
||||||
// based on.
|
// based on.
|
||||||
const tmplSourceGo = `
|
const tmplSourceGo = `
|
||||||
// This file is an automatically generated Go binding. Do not modify as any
|
// Code generated - DO NOT EDIT.
|
||||||
// change will likely be lost upon the next re-generation!
|
// This file is a generated binding and any manual changes will be lost.
|
||||||
|
|
||||||
package {{.Package}}
|
package {{.Package}}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,15 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadBool = errors.New("abi: improperly encoded boolean value")
|
||||||
|
)
|
||||||
|
|
||||||
// formatSliceString formats the reflection kind with the given slice size
|
// formatSliceString formats the reflection kind with the given slice size
|
||||||
// and returns a formatted string representation.
|
// and returns a formatted string representation.
|
||||||
func formatSliceString(kind reflect.Kind, sliceSize int) string {
|
func formatSliceString(kind reflect.Kind, sliceSize int) string {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ type Method struct {
|
||||||
Outputs []Argument
|
Outputs []Argument
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Method) pack(method Method, args ...interface{}) ([]byte, error) {
|
func (method Method) pack(args ...interface{}) ([]byte, error) {
|
||||||
// Make sure arguments match up and pack them
|
// Make sure arguments match up and pack them
|
||||||
if len(args) != len(method.Inputs) {
|
if len(args) != len(method.Inputs) {
|
||||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
|
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
|
||||||
|
|
|
||||||
|
|
@ -62,19 +62,6 @@ func U256(n *big.Int) []byte {
|
||||||
return math.PaddedBigBytes(math.U256(n), 32)
|
return math.PaddedBigBytes(math.U256(n), 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
|
|
||||||
func packNum(value reflect.Value) []byte {
|
|
||||||
switch kind := value.Kind(); kind {
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
return U256(new(big.Int).SetUint64(value.Uint()))
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return U256(big.NewInt(value.Int()))
|
|
||||||
case reflect.Ptr:
|
|
||||||
return U256(value.Interface().(*big.Int))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checks whether the given reflect value is signed. This also works for slices with a number type
|
// checks whether the given reflect value is signed. This also works for slices with a number type
|
||||||
func isSigned(v reflect.Value) bool {
|
func isSigned(v reflect.Value) bool {
|
||||||
switch v.Type() {
|
switch v.Type() {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -34,43 +33,6 @@ func TestNumberTypes(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPackNumber(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
value reflect.Value
|
|
||||||
packed []byte
|
|
||||||
}{
|
|
||||||
// Protocol limits
|
|
||||||
{reflect.ValueOf(0), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
|
|
||||||
{reflect.ValueOf(1), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
|
||||||
{reflect.ValueOf(-1), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
||||||
|
|
||||||
// Type corner cases
|
|
||||||
{reflect.ValueOf(uint8(math.MaxUint8)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255}},
|
|
||||||
{reflect.ValueOf(uint16(math.MaxUint16)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255}},
|
|
||||||
{reflect.ValueOf(uint32(math.MaxUint32)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255}},
|
|
||||||
{reflect.ValueOf(uint64(math.MaxUint64)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}},
|
|
||||||
|
|
||||||
{reflect.ValueOf(int8(math.MaxInt8)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127}},
|
|
||||||
{reflect.ValueOf(int16(math.MaxInt16)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255}},
|
|
||||||
{reflect.ValueOf(int32(math.MaxInt32)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255, 255, 255}},
|
|
||||||
{reflect.ValueOf(int64(math.MaxInt64)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255, 255, 255, 255, 255, 255, 255}},
|
|
||||||
|
|
||||||
{reflect.ValueOf(int8(math.MinInt8)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128}},
|
|
||||||
{reflect.ValueOf(int16(math.MinInt16)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 0}},
|
|
||||||
{reflect.ValueOf(int32(math.MinInt32)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 0, 0, 0}},
|
|
||||||
{reflect.ValueOf(int64(math.MinInt64)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 0, 0, 0, 0, 0, 0, 0}},
|
|
||||||
}
|
|
||||||
for i, tt := range tests {
|
|
||||||
packed := packNum(tt.value)
|
|
||||||
if !bytes.Equal(packed, tt.packed) {
|
|
||||||
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if packed := packNum(reflect.ValueOf("string")); packed != nil {
|
|
||||||
t.Errorf("expected 'string' to pack to nil. got %x instead", packed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSigned(t *testing.T) {
|
func TestSigned(t *testing.T) {
|
||||||
if isSigned(reflect.ValueOf(uint(10))) {
|
if isSigned(reflect.ValueOf(uint(10))) {
|
||||||
t.Error("signed")
|
t.Error("signed")
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -59,8 +60,20 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||||
if reflectValue.Kind() == reflect.Array {
|
if reflectValue.Kind() == reflect.Array {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
||||||
}
|
}
|
||||||
panic("abi: fatal error")
|
panic("abi: fatal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
|
||||||
|
func packNum(value reflect.Value) []byte {
|
||||||
|
switch kind := value.Kind(); kind {
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return U256(new(big.Int).SetUint64(value.Uint()))
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return U256(big.NewInt(value.Int()))
|
||||||
|
case reflect.Ptr:
|
||||||
|
return U256(value.Interface().(*big.Int))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
441
accounts/abi/pack_test.go
Normal file
441
accounts/abi/pack_test.go
Normal file
|
|
@ -0,0 +1,441 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPack(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
typ string
|
||||||
|
|
||||||
|
input interface{}
|
||||||
|
output []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"uint8",
|
||||||
|
uint8(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint8[]",
|
||||||
|
[]uint8{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint16",
|
||||||
|
uint16(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint16[]",
|
||||||
|
[]uint16{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint32",
|
||||||
|
uint32(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint32[]",
|
||||||
|
[]uint32{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint64",
|
||||||
|
uint64(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint64[]",
|
||||||
|
[]uint64{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint256",
|
||||||
|
big.NewInt(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint256[]",
|
||||||
|
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int8",
|
||||||
|
int8(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int8[]",
|
||||||
|
[]int8{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int16",
|
||||||
|
int16(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int16[]",
|
||||||
|
[]int16{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int32",
|
||||||
|
int32(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int32[]",
|
||||||
|
[]int32{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int64",
|
||||||
|
int64(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int64[]",
|
||||||
|
[]int64{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int256",
|
||||||
|
big.NewInt(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int256[]",
|
||||||
|
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes1",
|
||||||
|
[1]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes2",
|
||||||
|
[2]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes3",
|
||||||
|
[3]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes4",
|
||||||
|
[4]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes5",
|
||||||
|
[5]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes6",
|
||||||
|
[6]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes7",
|
||||||
|
[7]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes8",
|
||||||
|
[8]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes9",
|
||||||
|
[9]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes10",
|
||||||
|
[10]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes11",
|
||||||
|
[11]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes12",
|
||||||
|
[12]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes13",
|
||||||
|
[13]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes14",
|
||||||
|
[14]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes15",
|
||||||
|
[15]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes16",
|
||||||
|
[16]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes17",
|
||||||
|
[17]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes18",
|
||||||
|
[18]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes19",
|
||||||
|
[19]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes20",
|
||||||
|
[20]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes21",
|
||||||
|
[21]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes22",
|
||||||
|
[22]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes23",
|
||||||
|
[23]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes24",
|
||||||
|
[24]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes24",
|
||||||
|
[24]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes25",
|
||||||
|
[25]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes26",
|
||||||
|
[26]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes27",
|
||||||
|
[27]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes28",
|
||||||
|
[28]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes29",
|
||||||
|
[29]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes30",
|
||||||
|
[30]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes31",
|
||||||
|
[31]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes32",
|
||||||
|
[32]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address[]",
|
||||||
|
[]common.Address{{1}, {2}},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes32[]",
|
||||||
|
[]common.Hash{{1}, {2}},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function",
|
||||||
|
[24]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string",
|
||||||
|
"foobar",
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
typ, err := NewType(test.typ)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected parse error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := typ.pack(reflect.ValueOf(test.input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected pack error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(output, test.output) {
|
||||||
|
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodPack(t *testing.T) {
|
||||||
|
abi, err := JSON(strings.NewReader(jsondata2))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := abi.Methods["slice"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
||||||
|
packed, err := abi.Pack("slice", []uint32{1, 2})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrA, addrB = common.Address{1}, common.Address{2}
|
||||||
|
sig = abi.Methods["sliceAddress"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||||
|
|
||||||
|
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrC, addrD = common.Address{3}, common.Address{4}
|
||||||
|
sig = abi.Methods["sliceMultiAddress"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||||
|
|
||||||
|
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["slice256"].Id()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
||||||
|
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackNumber(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
value reflect.Value
|
||||||
|
packed []byte
|
||||||
|
}{
|
||||||
|
// Protocol limits
|
||||||
|
{reflect.ValueOf(0), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")},
|
||||||
|
{reflect.ValueOf(1), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||||
|
{reflect.ValueOf(-1), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")},
|
||||||
|
|
||||||
|
// Type corner cases
|
||||||
|
{reflect.ValueOf(uint8(math.MaxUint8)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")},
|
||||||
|
{reflect.ValueOf(uint16(math.MaxUint16)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000ffff")},
|
||||||
|
{reflect.ValueOf(uint32(math.MaxUint32)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000ffffffff")},
|
||||||
|
{reflect.ValueOf(uint64(math.MaxUint64)), common.Hex2Bytes("000000000000000000000000000000000000000000000000ffffffffffffffff")},
|
||||||
|
|
||||||
|
{reflect.ValueOf(int8(math.MaxInt8)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000007f")},
|
||||||
|
{reflect.ValueOf(int16(math.MaxInt16)), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000007fff")},
|
||||||
|
{reflect.ValueOf(int32(math.MaxInt32)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000007fffffff")},
|
||||||
|
{reflect.ValueOf(int64(math.MaxInt64)), common.Hex2Bytes("0000000000000000000000000000000000000000000000007fffffffffffffff")},
|
||||||
|
|
||||||
|
{reflect.ValueOf(int8(math.MinInt8)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80")},
|
||||||
|
{reflect.ValueOf(int16(math.MinInt16)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000")},
|
||||||
|
{reflect.ValueOf(int32(math.MinInt32)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000")},
|
||||||
|
{reflect.ValueOf(int64(math.MinInt64)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
packed := packNum(tt.value)
|
||||||
|
if !bytes.Equal(packed, tt.packed) {
|
||||||
|
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if packed := packNum(reflect.ValueOf("string")); packed != nil {
|
||||||
|
t.Errorf("expected 'string' to pack to nil. got %x instead", packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,30 +32,30 @@ func indirect(v reflect.Value) reflect.Value {
|
||||||
|
|
||||||
// reflectIntKind returns the reflect using the given size and
|
// reflectIntKind returns the reflect using the given size and
|
||||||
// unsignedness.
|
// unsignedness.
|
||||||
func reflectIntKind(unsigned bool, size int) reflect.Kind {
|
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
|
return reflect.Uint8, uint8_t
|
||||||
}
|
}
|
||||||
return reflect.Int8
|
return reflect.Int8, int8_t
|
||||||
case 16:
|
case 16:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint16
|
return reflect.Uint16, uint16_t
|
||||||
}
|
}
|
||||||
return reflect.Int16
|
return reflect.Int16, int16_t
|
||||||
case 32:
|
case 32:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint32
|
return reflect.Uint32, uint32_t
|
||||||
}
|
}
|
||||||
return reflect.Int32
|
return reflect.Int32, int32_t
|
||||||
case 64:
|
case 64:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint64
|
return reflect.Uint64, uint64_t
|
||||||
}
|
}
|
||||||
return reflect.Int64
|
return reflect.Int64, int64_t
|
||||||
}
|
}
|
||||||
return reflect.Ptr
|
return reflect.Ptr, big_t
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ const (
|
||||||
FixedBytesTy
|
FixedBytesTy
|
||||||
BytesTy
|
BytesTy
|
||||||
HashTy
|
HashTy
|
||||||
FixedpointTy
|
FixedPointTy
|
||||||
FunctionTy
|
FunctionTy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -126,13 +126,11 @@ func NewType(t string) (typ Type, err error) {
|
||||||
|
|
||||||
switch varType {
|
switch varType {
|
||||||
case "int":
|
case "int":
|
||||||
typ.Kind = reflectIntKind(false, varSize)
|
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
||||||
typ.Type = big_t
|
|
||||||
typ.Size = varSize
|
typ.Size = varSize
|
||||||
typ.T = IntTy
|
typ.T = IntTy
|
||||||
case "uint":
|
case "uint":
|
||||||
typ.Kind = reflectIntKind(true, varSize)
|
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
||||||
typ.Type = ubig_t
|
|
||||||
typ.Size = varSize
|
typ.Size = varSize
|
||||||
typ.T = UintTy
|
typ.T = UintTy
|
||||||
case "bool":
|
case "bool":
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,11 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
||||||
|
|
@ -31,26 +34,44 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
blob string
|
blob string
|
||||||
kind Type
|
kind Type
|
||||||
}{
|
}{
|
||||||
{"int", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
{"bool", Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}},
|
||||||
{"int8", Type{Kind: reflect.Int8, Type: big_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
{"bool[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Bool, T: BoolTy, Elem: &Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}, stringKind: "bool[]"}},
|
||||||
|
{"bool[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Bool, T: BoolTy, Elem: &Type{Kind: reflect.Bool, T: BoolTy, stringKind: "bool"}, stringKind: "bool[2]"}},
|
||||||
|
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
||||||
|
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
|
||||||
|
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
|
||||||
|
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
|
||||||
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
||||||
{"int[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
{"int8[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
||||||
{"int[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
{"int8[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
||||||
{"int32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int32, Type: big_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: big_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
{"int16[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
||||||
{"int32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int32, Type: big_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: big_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
{"int16[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
||||||
{"uint", Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
{"int32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
||||||
{"uint8", Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
{"int32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
||||||
{"uint256", Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
{"int64[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
||||||
{"uint[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
{"int64[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
||||||
{"uint[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: ubig_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
{"int256[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
||||||
{"uint32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint32, Type: ubig_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: big_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
{"int256[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
||||||
{"uint32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint32, Type: ubig_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: big_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||||
{"bytes", Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}},
|
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||||
{"bytes32", Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}},
|
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||||
{"bytes[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||||
{"bytes[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
||||||
{"bytes32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
{"uint8[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
||||||
{"bytes32[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: ubig_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
{"uint8[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
||||||
|
{"uint16[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
||||||
|
{"uint16[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
||||||
|
{"uint32[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
||||||
|
{"uint32[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
||||||
|
{"uint64[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
||||||
|
{"uint64[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
||||||
|
{"uint256[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
||||||
|
{"uint256[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
||||||
|
{"bytes32", Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}},
|
||||||
|
{"bytes[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
||||||
|
{"bytes[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsSlice: true, SliceSize: -1, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
||||||
|
{"bytes32[]", Type{IsSlice: true, SliceSize: -1, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
||||||
|
{"bytes32[2]", Type{IsArray: true, SliceSize: 2, Elem: &Type{IsArray: true, SliceSize: 32, Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, T: FixedBytesTy, stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
||||||
{"string", Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}},
|
{"string", Type{Kind: reflect.String, Size: -1, T: StringTy, stringKind: "string"}},
|
||||||
{"string[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[]"}},
|
{"string[]", Type{IsSlice: true, SliceSize: -1, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[]"}},
|
||||||
{"string[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[2]"}},
|
{"string[2]", Type{IsArray: true, SliceSize: 2, Kind: reflect.String, T: StringTy, Size: -1, Elem: &Type{Kind: reflect.String, T: StringTy, Size: -1, stringKind: "string"}, stringKind: "string[2]"}},
|
||||||
|
|
@ -76,3 +97,59 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTypeCheck(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
typ string
|
||||||
|
input interface{}
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{"uint", big.NewInt(1), ""},
|
||||||
|
{"int", big.NewInt(1), ""},
|
||||||
|
{"uint30", big.NewInt(1), ""},
|
||||||
|
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||||
|
{"uint16", uint16(1), ""},
|
||||||
|
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||||
|
{"uint16[]", []uint16{1, 2, 3}, ""},
|
||||||
|
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
||||||
|
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type []uint16 as argument"},
|
||||||
|
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
||||||
|
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
|
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
||||||
|
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
|
{"address[]", []common.Address{{1}}, ""},
|
||||||
|
{"address[1]", []common.Address{{1}}, ""},
|
||||||
|
{"address[1]", [1]common.Address{{1}}, ""},
|
||||||
|
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||||
|
{"bytes32", [32]byte{}, ""},
|
||||||
|
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||||
|
{"bytes32", common.Hash{1}, ""},
|
||||||
|
{"bytes31", [31]byte{}, ""},
|
||||||
|
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||||
|
{"bytes", []byte{0, 1}, ""},
|
||||||
|
{"bytes", [2]byte{0, 1}, ""},
|
||||||
|
{"bytes", common.Hash{1}, ""},
|
||||||
|
{"string", "hello world", ""},
|
||||||
|
{"bytes32[]", [][32]byte{{}}, ""},
|
||||||
|
{"function", [24]byte{}, ""},
|
||||||
|
} {
|
||||||
|
typ, err := NewType(test.typ)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("unexpected parse error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = typeCheck(typ, reflect.ValueOf(test.input))
|
||||||
|
if err != nil && len(test.err) == 0 {
|
||||||
|
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil && len(test.err) != 0 {
|
||||||
|
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
||||||
|
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
235
accounts/abi/unpack.go
Normal file
235
accounts/abi/unpack.go
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// toGoSliceType parses the input and casts it to the proper slice defined by the ABI
|
||||||
|
// argument in T.
|
||||||
|
func toGoSlice(i int, t Argument, output []byte) (interface{}, error) {
|
||||||
|
index := i * 32
|
||||||
|
// The slice must, at very least be large enough for the index+32 which is exactly the size required
|
||||||
|
// for the [offset in output, size of offset].
|
||||||
|
if index+32 > len(output) {
|
||||||
|
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32)
|
||||||
|
}
|
||||||
|
elem := t.Type.Elem
|
||||||
|
|
||||||
|
// first we need to create a slice of the type
|
||||||
|
var refSlice reflect.Value
|
||||||
|
switch elem.T {
|
||||||
|
case IntTy, UintTy, BoolTy:
|
||||||
|
// create a new reference slice matching the element type
|
||||||
|
switch t.Type.Kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
refSlice = reflect.ValueOf([]bool(nil))
|
||||||
|
case reflect.Uint8:
|
||||||
|
refSlice = reflect.ValueOf([]uint8(nil))
|
||||||
|
case reflect.Uint16:
|
||||||
|
refSlice = reflect.ValueOf([]uint16(nil))
|
||||||
|
case reflect.Uint32:
|
||||||
|
refSlice = reflect.ValueOf([]uint32(nil))
|
||||||
|
case reflect.Uint64:
|
||||||
|
refSlice = reflect.ValueOf([]uint64(nil))
|
||||||
|
case reflect.Int8:
|
||||||
|
refSlice = reflect.ValueOf([]int8(nil))
|
||||||
|
case reflect.Int16:
|
||||||
|
refSlice = reflect.ValueOf([]int16(nil))
|
||||||
|
case reflect.Int32:
|
||||||
|
refSlice = reflect.ValueOf([]int32(nil))
|
||||||
|
case reflect.Int64:
|
||||||
|
refSlice = reflect.ValueOf([]int64(nil))
|
||||||
|
default:
|
||||||
|
refSlice = reflect.ValueOf([]*big.Int(nil))
|
||||||
|
}
|
||||||
|
case AddressTy: // address must be of slice Address
|
||||||
|
refSlice = reflect.ValueOf([]common.Address(nil))
|
||||||
|
case HashTy: // hash must be of slice hash
|
||||||
|
refSlice = reflect.ValueOf([]common.Hash(nil))
|
||||||
|
case FixedBytesTy:
|
||||||
|
refSlice = reflect.ValueOf([][]byte(nil))
|
||||||
|
default: // no other types are supported
|
||||||
|
return nil, fmt.Errorf("abi: unsupported slice type %v", elem.T)
|
||||||
|
}
|
||||||
|
|
||||||
|
var slice []byte
|
||||||
|
var size int
|
||||||
|
var offset int
|
||||||
|
if t.Type.IsSlice {
|
||||||
|
// get the offset which determines the start of this array ...
|
||||||
|
offset = int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
||||||
|
if offset+32 > len(output) {
|
||||||
|
return nil, fmt.Errorf("abi: cannot marshal in to go slice: offset %d would go over slice boundary (len=%d)", len(output), offset+32)
|
||||||
|
}
|
||||||
|
|
||||||
|
slice = output[offset:]
|
||||||
|
// ... starting with the size of the array in elements ...
|
||||||
|
size = int(binary.BigEndian.Uint64(slice[24:32]))
|
||||||
|
slice = slice[32:]
|
||||||
|
// ... and make sure that we've at the very least the amount of bytes
|
||||||
|
// available in the buffer.
|
||||||
|
if size*32 > len(slice) {
|
||||||
|
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), offset+32+size*32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reslice to match the required size
|
||||||
|
slice = slice[:size*32]
|
||||||
|
} else if t.Type.IsArray {
|
||||||
|
//get the number of elements in the array
|
||||||
|
size = t.Type.SliceSize
|
||||||
|
|
||||||
|
//check to make sure array size matches up
|
||||||
|
if index+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), index+32*size)
|
||||||
|
}
|
||||||
|
//slice is there for a fixed amount of times
|
||||||
|
slice = output[index : index+size*32]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
var (
|
||||||
|
inter interface{} // interface type
|
||||||
|
returnOutput = slice[i*32 : i*32+32] // the return output
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// set inter to the correct type (cast)
|
||||||
|
switch elem.T {
|
||||||
|
case IntTy, UintTy:
|
||||||
|
inter = readInteger(t.Type.Kind, returnOutput)
|
||||||
|
case BoolTy:
|
||||||
|
inter, err = readBool(returnOutput)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case AddressTy:
|
||||||
|
inter = common.BytesToAddress(returnOutput)
|
||||||
|
case HashTy:
|
||||||
|
inter = common.BytesToHash(returnOutput)
|
||||||
|
case FixedBytesTy:
|
||||||
|
inter = returnOutput
|
||||||
|
}
|
||||||
|
// append the item to our reflect slice
|
||||||
|
refSlice = reflect.Append(refSlice, reflect.ValueOf(inter))
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the interface
|
||||||
|
return refSlice.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||||
|
switch kind {
|
||||||
|
case reflect.Uint8:
|
||||||
|
return uint8(b[len(b)-1])
|
||||||
|
case reflect.Uint16:
|
||||||
|
return binary.BigEndian.Uint16(b[len(b)-2:])
|
||||||
|
case reflect.Uint32:
|
||||||
|
return binary.BigEndian.Uint32(b[len(b)-4:])
|
||||||
|
case reflect.Uint64:
|
||||||
|
return binary.BigEndian.Uint64(b[len(b)-8:])
|
||||||
|
case reflect.Int8:
|
||||||
|
return int8(b[len(b)-1])
|
||||||
|
case reflect.Int16:
|
||||||
|
return int16(binary.BigEndian.Uint16(b[len(b)-2:]))
|
||||||
|
case reflect.Int32:
|
||||||
|
return int32(binary.BigEndian.Uint32(b[len(b)-4:]))
|
||||||
|
case reflect.Int64:
|
||||||
|
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
|
||||||
|
default:
|
||||||
|
return new(big.Int).SetBytes(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readBool(word []byte) (bool, error) {
|
||||||
|
if len(word) != 32 {
|
||||||
|
return false, fmt.Errorf("abi: fatal error: incorrect word length")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, b := range word {
|
||||||
|
if b != 0 && i != 31 {
|
||||||
|
return false, errBadBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch word[31] {
|
||||||
|
case 0:
|
||||||
|
return false, nil
|
||||||
|
case 1:
|
||||||
|
return true, nil
|
||||||
|
default:
|
||||||
|
return false, errBadBool
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// toGoType parses the input and casts it to the proper type defined by the ABI
|
||||||
|
// argument in T.
|
||||||
|
func toGoType(i int, t Argument, output []byte) (interface{}, error) {
|
||||||
|
// we need to treat slices differently
|
||||||
|
if (t.Type.IsSlice || t.Type.IsArray) && t.Type.T != BytesTy && t.Type.T != StringTy && t.Type.T != FixedBytesTy && t.Type.T != FunctionTy {
|
||||||
|
return toGoSlice(i, t, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
index := i * 32
|
||||||
|
if index+32 > len(output) {
|
||||||
|
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the given index output and check whether we need to read
|
||||||
|
// a different offset and length based on the type (i.e. string, bytes)
|
||||||
|
var returnOutput []byte
|
||||||
|
switch t.Type.T {
|
||||||
|
case StringTy, BytesTy: // variable arrays are written at the end of the return bytes
|
||||||
|
// parse offset from which we should start reading
|
||||||
|
offset := int(binary.BigEndian.Uint64(output[index+24 : index+32]))
|
||||||
|
if offset+32 > len(output) {
|
||||||
|
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32)
|
||||||
|
}
|
||||||
|
// parse the size up until we should be reading
|
||||||
|
size := int(binary.BigEndian.Uint64(output[offset+24 : offset+32]))
|
||||||
|
if offset+32+size > len(output) {
|
||||||
|
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), offset+32+size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the bytes for this return value
|
||||||
|
returnOutput = output[offset+32 : offset+32+size]
|
||||||
|
default:
|
||||||
|
returnOutput = output[index : index+32]
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the bytes to whatever is specified by the ABI.
|
||||||
|
switch t.Type.T {
|
||||||
|
case IntTy, UintTy:
|
||||||
|
return readInteger(t.Type.Kind, returnOutput), nil
|
||||||
|
case BoolTy:
|
||||||
|
return readBool(returnOutput)
|
||||||
|
case AddressTy:
|
||||||
|
return common.BytesToAddress(returnOutput), nil
|
||||||
|
case HashTy:
|
||||||
|
return common.BytesToHash(returnOutput), nil
|
||||||
|
case BytesTy, FixedBytesTy, FunctionTy:
|
||||||
|
return returnOutput, nil
|
||||||
|
case StringTy:
|
||||||
|
return string(returnOutput), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("abi: unknown type %v", t.Type.T)
|
||||||
|
}
|
||||||
681
accounts/abi/unpack_test.go
Normal file
681
accounts/abi/unpack_test.go
Normal file
|
|
@ -0,0 +1,681 @@
|
||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimpleMethodUnpack(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
def string // definition of the **output** ABI params
|
||||||
|
marshalledOutput []byte // evm return data
|
||||||
|
expectedOut interface{} // the expected output
|
||||||
|
outVar string // the output variable (e.g. uint32, *big.Int, etc)
|
||||||
|
err string // empty or error if expected
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`[ { "type": "bool" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
bool(true),
|
||||||
|
"bool",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "uint32" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
uint32(1),
|
||||||
|
"uint32",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "uint32" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
nil,
|
||||||
|
"uint16",
|
||||||
|
"abi: cannot unmarshal uint32 in to uint16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "uint17" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
nil,
|
||||||
|
"uint16",
|
||||||
|
"abi: cannot unmarshal *big.Int in to uint16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "uint17" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
big.NewInt(1),
|
||||||
|
"*big.Int",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`[ { "type": "int32" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
int32(1),
|
||||||
|
"int32",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "int32" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
nil,
|
||||||
|
"int16",
|
||||||
|
"abi: cannot unmarshal int32 in to int16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "int17" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
nil,
|
||||||
|
"int16",
|
||||||
|
"abi: cannot unmarshal *big.Int in to int16",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "int17" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||||
|
big.NewInt(1),
|
||||||
|
"*big.Int",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
`[ { "type": "address" } ]`,
|
||||||
|
common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"),
|
||||||
|
common.Address{1},
|
||||||
|
"address",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "bytes32" } ]`,
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
"bytes",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "bytes32" } ]`,
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
"hash",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "bytes32" } ]`,
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
"interface",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[ { "type": "function" } ]`,
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
[24]byte{1},
|
||||||
|
"function",
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
||||||
|
abi, err := JSON(strings.NewReader(abiDefinition))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d failed. %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var outvar interface{}
|
||||||
|
switch test.outVar {
|
||||||
|
case "bool":
|
||||||
|
var v bool
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "uint8":
|
||||||
|
var v uint8
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "uint16":
|
||||||
|
var v uint16
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "uint32":
|
||||||
|
var v uint32
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "uint64":
|
||||||
|
var v uint64
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "int8":
|
||||||
|
var v int8
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "int16":
|
||||||
|
var v int16
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "int32":
|
||||||
|
var v int32
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "int64":
|
||||||
|
var v int64
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "*big.Int":
|
||||||
|
var v *big.Int
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "address":
|
||||||
|
var v common.Address
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "bytes":
|
||||||
|
var v []byte
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "hash":
|
||||||
|
var v common.Hash
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v.Bytes()[:]
|
||||||
|
case "function":
|
||||||
|
var v [24]byte
|
||||||
|
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||||
|
outvar = v
|
||||||
|
case "interface":
|
||||||
|
err = abi.Unpack(&outvar, "method", test.marshalledOutput)
|
||||||
|
default:
|
||||||
|
t.Errorf("unsupported type '%v' please add it to the switch statement in this test", test.outVar)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && len(test.err) == 0 {
|
||||||
|
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil && len(test.err) != 0 {
|
||||||
|
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
||||||
|
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
if !reflect.DeepEqual(test.expectedOut, outvar) {
|
||||||
|
t.Errorf("%d failed. Output error: expected %v, got %v", i, test.expectedOut, outvar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackSetInterfaceSlice(t *testing.T) {
|
||||||
|
var (
|
||||||
|
var1 = new(uint8)
|
||||||
|
var2 = new(uint8)
|
||||||
|
)
|
||||||
|
out := []interface{}{var1, var2}
|
||||||
|
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint8"}, {"type":"uint8"}]}]`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
marshalledReturn := append(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")...)
|
||||||
|
err = abi.Unpack(&out, "ints", marshalledReturn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if *var1 != 1 {
|
||||||
|
t.Error("expected var1 to be 1, got", *var1)
|
||||||
|
}
|
||||||
|
if *var2 != 2 {
|
||||||
|
t.Error("expected var2 to be 2, got", *var2)
|
||||||
|
}
|
||||||
|
|
||||||
|
out = []interface{}{var1}
|
||||||
|
err = abi.Unpack(&out, "ints", marshalledReturn)
|
||||||
|
|
||||||
|
expErr := "abi: cannot marshal in to slices of unequal size (require: 2, got: 1)"
|
||||||
|
if err == nil || err.Error() != expErr {
|
||||||
|
t.Error("expected err:", expErr, "Got:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackSetInterfaceArrayOutput(t *testing.T) {
|
||||||
|
var (
|
||||||
|
var1 = new([1]uint32)
|
||||||
|
var2 = new([1]uint32)
|
||||||
|
)
|
||||||
|
out := []interface{}{var1, var2}
|
||||||
|
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint32[1]"}, {"type":"uint32[1]"}]}]`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
marshalledReturn := append(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")...)
|
||||||
|
err = abi.Unpack(&out, "ints", marshalledReturn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *var1 != [1]uint32{1} {
|
||||||
|
t.Error("expected var1 to be [1], got", *var1)
|
||||||
|
}
|
||||||
|
if *var2 != [1]uint32{2} {
|
||||||
|
t.Error("expected var2 to be [2], got", *var2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiReturnWithStruct(t *testing.T) {
|
||||||
|
const definition = `[
|
||||||
|
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// using buff to make the code readable
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||||
|
stringOut := "hello"
|
||||||
|
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||||
|
|
||||||
|
var inter struct {
|
||||||
|
Int *big.Int
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
err = abi.Unpack(&inter, "multi", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inter.Int == nil || inter.Int.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Error("expected Int to be 1 got", inter.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
if inter.String != stringOut {
|
||||||
|
t.Error("expected String to be", stringOut, "got", inter.String)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reversed struct {
|
||||||
|
String string
|
||||||
|
Int *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
err = abi.Unpack(&reversed, "multi", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reversed.Int == nil || reversed.Int.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Error("expected Int to be 1 got", reversed.Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reversed.String != stringOut {
|
||||||
|
t.Error("expected String to be", stringOut, "got", reversed.String)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiReturnWithSlice(t *testing.T) {
|
||||||
|
const definition = `[
|
||||||
|
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// using buff to make the code readable
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||||
|
stringOut := "hello"
|
||||||
|
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||||
|
|
||||||
|
var inter []interface{}
|
||||||
|
err = abi.Unpack(&inter, "multi", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inter) != 2 {
|
||||||
|
t.Fatal("expected 2 results got", len(inter))
|
||||||
|
}
|
||||||
|
|
||||||
|
if num, ok := inter[0].(*big.Int); !ok || num.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Error("expected index 0 to be 1 got", num)
|
||||||
|
}
|
||||||
|
|
||||||
|
if str, ok := inter[1].(string); !ok || str != stringOut {
|
||||||
|
t.Error("expected index 1 to be", stringOut, "got", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalArrays(t *testing.T) {
|
||||||
|
const definition = `[
|
||||||
|
{ "name" : "bytes32", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||||
|
{ "name" : "bytes10", "constant" : false, "outputs": [ { "type": "bytes10" } ] }
|
||||||
|
]`
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := common.LeftPadBytes([]byte{1}, 32)
|
||||||
|
|
||||||
|
var bytes10 [10]byte
|
||||||
|
err = abi.Unpack(&bytes10, "bytes32", output)
|
||||||
|
if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
|
||||||
|
t.Error("expected error or bytes32 not be assignable to bytes10:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes32 [32]byte
|
||||||
|
err = abi.Unpack(&bytes32, "bytes32", output)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("didn't expect error:", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(bytes32[:], output) {
|
||||||
|
t.Error("expected bytes32[31] to be 1 got", bytes32[31])
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
B10 [10]byte
|
||||||
|
B32 [32]byte
|
||||||
|
)
|
||||||
|
|
||||||
|
var b10 B10
|
||||||
|
err = abi.Unpack(&b10, "bytes32", output)
|
||||||
|
if err == nil || err.Error() != "abi: cannot unmarshal src (len=32) in to dst (len=10)" {
|
||||||
|
t.Error("expected error or bytes32 not be assignable to bytes10:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var b32 B32
|
||||||
|
err = abi.Unpack(&b32, "bytes32", output)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("didn't expect error:", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(b32[:], output) {
|
||||||
|
t.Error("expected bytes32[31] to be 1 got", bytes32[31])
|
||||||
|
}
|
||||||
|
|
||||||
|
output[10] = 1
|
||||||
|
var shortAssignLong [32]byte
|
||||||
|
err = abi.Unpack(&shortAssignLong, "bytes10", output)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("didn't expect error:", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(output, shortAssignLong[:]) {
|
||||||
|
t.Errorf("expected %x to be %x", shortAssignLong, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshal(t *testing.T) {
|
||||||
|
const definition = `[
|
||||||
|
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
||||||
|
{ "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] },
|
||||||
|
{ "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] },
|
||||||
|
{ "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||||
|
{ "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
|
||||||
|
{ "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] },
|
||||||
|
{ "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] },
|
||||||
|
{ "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
|
||||||
|
{ "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// marshal int
|
||||||
|
var Int *big.Int
|
||||||
|
err = abi.Unpack(&Int, "int", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Int == nil || Int.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
t.Error("expected Int to be 1 got", Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal bool
|
||||||
|
var Bool bool
|
||||||
|
err = abi.Unpack(&Bool, "bool", common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Bool {
|
||||||
|
t.Error("expected Bool to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal dynamic bytes max length 32
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
bytesOut := common.RightPadBytes([]byte("hello"), 32)
|
||||||
|
buff.Write(bytesOut)
|
||||||
|
|
||||||
|
var Bytes []byte
|
||||||
|
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(Bytes, bytesOut) {
|
||||||
|
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshall dynamic bytes max length 64
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||||
|
bytesOut = common.RightPadBytes([]byte("hello"), 64)
|
||||||
|
buff.Write(bytesOut)
|
||||||
|
|
||||||
|
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(Bytes, bytesOut) {
|
||||||
|
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshall dynamic bytes max length 63
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000003f"))
|
||||||
|
bytesOut = common.RightPadBytes([]byte("hello"), 63)
|
||||||
|
buff.Write(bytesOut)
|
||||||
|
|
||||||
|
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(Bytes, bytesOut) {
|
||||||
|
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal dynamic bytes output empty
|
||||||
|
err = abi.Unpack(&Bytes, "bytes", nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal dynamic bytes length 5
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||||
|
buff.Write(common.RightPadBytes([]byte("hello"), 32))
|
||||||
|
|
||||||
|
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(Bytes, []byte("hello")) {
|
||||||
|
t.Errorf("expected %x got %x", bytesOut, Bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal dynamic bytes length 5
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.RightPadBytes([]byte("hello"), 32))
|
||||||
|
|
||||||
|
var hash common.Hash
|
||||||
|
err = abi.Unpack(&hash, "fixed", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helloHash := common.BytesToHash(common.RightPadBytes([]byte("hello"), 32))
|
||||||
|
if hash != helloHash {
|
||||||
|
t.Errorf("Expected %x to equal %x", hash, helloHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal error
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
err = abi.Unpack(&Bytes, "bytes", buff.Bytes())
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = abi.Unpack(&Bytes, "multi", make([]byte, 64))
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal mixed bytes
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||||
|
fixed := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")
|
||||||
|
buff.Write(fixed)
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020"))
|
||||||
|
bytesOut = common.RightPadBytes([]byte("hello"), 32)
|
||||||
|
buff.Write(bytesOut)
|
||||||
|
|
||||||
|
var out []interface{}
|
||||||
|
err = abi.Unpack(&out, "mixedBytes", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("didn't expect error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(bytesOut, out[0].([]byte)) {
|
||||||
|
t.Errorf("expected %x, got %x", bytesOut, out[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(fixed, out[1].([]byte)) {
|
||||||
|
t.Errorf("expected %x, got %x", fixed, out[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000003"))
|
||||||
|
// marshal int array
|
||||||
|
var intArray [3]*big.Int
|
||||||
|
err = abi.Unpack(&intArray, "intArraySingle", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
var testAgainstIntArray [3]*big.Int
|
||||||
|
testAgainstIntArray[0] = big.NewInt(1)
|
||||||
|
testAgainstIntArray[1] = big.NewInt(2)
|
||||||
|
testAgainstIntArray[2] = big.NewInt(3)
|
||||||
|
|
||||||
|
for i, Int := range intArray {
|
||||||
|
if Int.Cmp(testAgainstIntArray[i]) != 0 {
|
||||||
|
t.Errorf("expected %v, got %v", testAgainstIntArray[i], Int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// marshal address slice
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020")) // offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"))
|
||||||
|
|
||||||
|
var outAddr []common.Address
|
||||||
|
err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("didn't expect error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outAddr) != 1 {
|
||||||
|
t.Fatal("expected 1 item, got", len(outAddr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if outAddr[0] != (common.Address{1}) {
|
||||||
|
t.Errorf("expected %x, got %x", common.Address{1}, outAddr[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal multiple address slice
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // offset
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // size
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000100000000000000000000000000000000000000"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // size
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000200000000000000000000000000000000000000"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000300000000000000000000000000000000000000"))
|
||||||
|
|
||||||
|
var outAddrStruct struct {
|
||||||
|
A []common.Address
|
||||||
|
B []common.Address
|
||||||
|
}
|
||||||
|
err = abi.Unpack(&outAddrStruct, "addressSliceDouble", buff.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("didn't expect error:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outAddrStruct.A) != 1 {
|
||||||
|
t.Fatal("expected 1 item, got", len(outAddrStruct.A))
|
||||||
|
}
|
||||||
|
|
||||||
|
if outAddrStruct.A[0] != (common.Address{1}) {
|
||||||
|
t.Errorf("expected %x, got %x", common.Address{1}, outAddrStruct.A[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outAddrStruct.B) != 2 {
|
||||||
|
t.Fatal("expected 1 item, got", len(outAddrStruct.B))
|
||||||
|
}
|
||||||
|
|
||||||
|
if outAddrStruct.B[0] != (common.Address{2}) {
|
||||||
|
t.Errorf("expected %x, got %x", common.Address{2}, outAddrStruct.B[0])
|
||||||
|
}
|
||||||
|
if outAddrStruct.B[1] != (common.Address{3}) {
|
||||||
|
t.Errorf("expected %x, got %x", common.Address{3}, outAddrStruct.B[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshal invalid address slice
|
||||||
|
buff.Reset()
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000100"))
|
||||||
|
|
||||||
|
err = abi.Unpack(&outAddr, "addressSliceSingle", buff.Bytes())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ var ErrNotSupported = errors.New("not supported")
|
||||||
var ErrInvalidPassphrase = errors.New("invalid passphrase")
|
var ErrInvalidPassphrase = errors.New("invalid passphrase")
|
||||||
|
|
||||||
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
||||||
// secodn time.
|
// second time.
|
||||||
var ErrWalletAlreadyOpen = errors.New("wallet already open")
|
var ErrWalletAlreadyOpen = errors.New("wallet already open")
|
||||||
|
|
||||||
// ErrWalletClosed is returned if a wallet is attempted to be opened the
|
// ErrWalletClosed is returned if a wallet is attempted to be opened the
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 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
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 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
|
||||||
|
|
|
||||||
|
|
@ -124,14 +124,13 @@ func (k *Key) UnmarshalJSON(j []byte) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey)
|
||||||
privkey, err := hex.DecodeString(keyJSON.PrivateKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
k.Address = common.BytesToAddress(addr)
|
k.Address = common.BytesToAddress(addr)
|
||||||
k.PrivateKey = crypto.ToECDSA(privkey)
|
k.PrivateKey = privkey
|
||||||
|
|
||||||
return nil
|
return 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
|
||||||
|
|
@ -450,7 +450,6 @@ func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (acco
|
||||||
if ks.cache.hasAddress(key.Address) {
|
if ks.cache.hasAddress(key.Address) {
|
||||||
return accounts.Account{}, fmt.Errorf("account already exists")
|
return accounts.Account{}, fmt.Errorf("account already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ks.importKey(key, passphrase)
|
return ks.importKey(key, passphrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,8 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key := crypto.ToECDSA(keyBytes)
|
key := crypto.ToECDSAUnsafe(keyBytes)
|
||||||
|
|
||||||
return &Key{
|
return &Key{
|
||||||
Id: uuid.UUID(keyId),
|
Id: uuid.UUID(keyId),
|
||||||
Address: crypto.PubkeyToAddress(key.PublicKey),
|
Address: crypto.PubkeyToAddress(key.PublicKey),
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ func TestKeyEncryptDecrypt(t *testing.T) {
|
||||||
// Decrypt with the correct password
|
// Decrypt with the correct password
|
||||||
key, err := DecryptKey(keyjson, password)
|
key, err := DecryptKey(keyjson, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("test %d: json key failed to decrypt: %v", i, err)
|
t.Fatalf("test %d: json key failed to decrypt: %v", i, err)
|
||||||
}
|
}
|
||||||
if key.Address != address {
|
if key.Address != address {
|
||||||
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
|
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -140,21 +141,32 @@ func TestV3_PBKDF2_1(t *testing.T) {
|
||||||
testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
|
testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests")
|
||||||
|
|
||||||
|
func skipIfSubmoduleMissing(t *testing.T) {
|
||||||
|
if !common.FileExist(testsSubmodule) {
|
||||||
|
t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestV3_PBKDF2_2(t *testing.T) {
|
func TestV3_PBKDF2_2(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t)
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
testDecryptV3(tests["test1"], t)
|
testDecryptV3(tests["test1"], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV3_PBKDF2_3(t *testing.T) {
|
func TestV3_PBKDF2_3(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t)
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
|
testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV3_PBKDF2_4(t *testing.T) {
|
func TestV3_PBKDF2_4(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t)
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
testDecryptV3(tests["evilnonce"], t)
|
testDecryptV3(tests["evilnonce"], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,8 +177,9 @@ func TestV3_Scrypt_1(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestV3_Scrypt_2(t *testing.T) {
|
func TestV3_Scrypt_2(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
tests := loadKeyStoreTestV3("../../tests/files/KeyStoreTests/basic_tests.json", t)
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
testDecryptV3(tests["test2"], t)
|
testDecryptV3(tests["test2"], t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,8 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ethPriv := crypto.Keccak256(plainText)
|
ethPriv := crypto.Keccak256(plainText)
|
||||||
ecKey := crypto.ToECDSA(ethPriv)
|
ecKey := crypto.ToECDSAUnsafe(ethPriv)
|
||||||
|
|
||||||
key = &Key{
|
key = &Key{
|
||||||
Id: nil,
|
Id: nil,
|
||||||
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
|
Address: crypto.PubkeyToAddress(ecKey.PublicKey),
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@ environment:
|
||||||
PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH%
|
PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH%
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.8.3.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.8.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.8.3.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ for Ubuntu. Packages are built for the all Ubuntu versions which are supported b
|
||||||
Canonical:
|
Canonical:
|
||||||
|
|
||||||
- Trusty Tahr (14.04 LTS)
|
- Trusty Tahr (14.04 LTS)
|
||||||
- Wily Werewolf (15.10)
|
|
||||||
- Xenial Xerus (16.04 LTS)
|
- Xenial Xerus (16.04 LTS)
|
||||||
- Yakkety Yak (16.10)
|
- 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.
|
||||||
|
|
@ -21,18 +21,18 @@ variable which Travis CI makes available to certain builds.
|
||||||
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
|
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.7, which is co-installable alongside the regular golang package. PPA dependencies
|
golang-1.8, 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.7 and Debian packaging tools:
|
Add the gophers PPA and install Go 1.8 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.7 devscripts debhelper
|
$ sudo apt-get install build-essential golang-1.8 devscripts debhelper
|
||||||
|
|
||||||
Create the source packages:
|
Create the source packages:
|
||||||
|
|
||||||
|
|
@ -40,10 +40,10 @@ Create the source packages:
|
||||||
|
|
||||||
Then go into the source package directory for your running distribution and build the package:
|
Then go into the source package directory for your running distribution and build the package:
|
||||||
|
|
||||||
$ cd dist/ethereum-unstable-1.5.0+xenial
|
$ cd dist/ethereum-unstable-1.6.0+xenial
|
||||||
$ dpkg-buildpackage
|
$ dpkg-buildpackage
|
||||||
|
|
||||||
Built packages are placed in the dist/ directory.
|
Built packages are placed in the dist/ directory.
|
||||||
|
|
||||||
$ cd ..
|
$ cd ..
|
||||||
$ dpkg-deb -c geth-unstable_1.5.0+xenial_amd64.deb
|
$ dpkg-deb -c geth-unstable_1.6.0+xenial_amd64.deb
|
||||||
|
|
|
||||||
35
build/ci.go
35
build/ci.go
|
|
@ -74,42 +74,52 @@ var (
|
||||||
executablePath("bootnode"),
|
executablePath("bootnode"),
|
||||||
executablePath("evm"),
|
executablePath("evm"),
|
||||||
executablePath("geth"),
|
executablePath("geth"),
|
||||||
executablePath("swarm"),
|
executablePath("puppeth"),
|
||||||
executablePath("rlpdump"),
|
executablePath("rlpdump"),
|
||||||
|
executablePath("swarm"),
|
||||||
|
executablePath("wnode"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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: "geth",
|
Name: "abigen",
|
||||||
Description: "Ethereum CLI client.",
|
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bootnode",
|
Name: "bootnode",
|
||||||
Description: "Ethereum bootnode.",
|
Description: "Ethereum bootnode.",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "rlpdump",
|
|
||||||
Description: "Developer utility tool that prints RLP structures.",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "evm",
|
Name: "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",
|
||||||
|
Description: "Ethereum CLI client.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "puppeth",
|
||||||
|
Description: "Ethereum private network manager.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "rlpdump",
|
||||||
|
Description: "Developer utility tool that prints RLP structures.",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "swarm",
|
Name: "swarm",
|
||||||
Description: "Ethereum Swarm daemon and tools",
|
Description: "Ethereum Swarm daemon and tools",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "abigen",
|
Name: "wnode",
|
||||||
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
Description: "Ethereum Whisper diagnostic tool",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
debDistros = []string{"trusty", "xenial", "yakkety"}
|
debDistros = []string{"trusty", "xenial", "yakkety", "zesty"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
@ -165,7 +175,7 @@ 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 runtime.Version() < "go1.7" && !strings.HasPrefix(runtime.Version(), "devel") {
|
if runtime.Version() < "go1.7" && !strings.Contains(runtime.Version(), "devel") {
|
||||||
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.7 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.")
|
||||||
|
|
@ -287,7 +297,8 @@ func doTest(cmdline []string) {
|
||||||
// Run analysis tools before the tests.
|
// Run analysis tools before the tests.
|
||||||
build.MustRun(goTool("vet", packages...))
|
build.MustRun(goTool("vet", packages...))
|
||||||
if *misspell {
|
if *misspell {
|
||||||
spellcheck(packages)
|
// TODO(karalabe): Reenable after false detection is fixed: https://github.com/client9/misspell/issues/105
|
||||||
|
// spellcheck(packages)
|
||||||
}
|
}
|
||||||
// Run the actual tests.
|
// Run the actual tests.
|
||||||
gotest := goTool("test", buildFlags(env)...)
|
gotest := goTool("test", buildFlags(env)...)
|
||||||
|
|
|
||||||
|
|
@ -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.7
|
Build-Depends: debhelper (>= 8.0.0), golang-1.8
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#export DH_VERBOSE=1
|
#export DH_VERBOSE=1
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
build/env.sh /usr/lib/go-1.7/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}}
|
build/env.sh /usr/lib/go-1.8/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:
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,15 +45,20 @@ var (
|
||||||
// paths with any of these prefixes will be skipped
|
// paths with any of these prefixes will be skipped
|
||||||
skipPrefixes = []string{
|
skipPrefixes = []string{
|
||||||
// boring stuff
|
// boring stuff
|
||||||
"vendor/", "tests/files/", "build/",
|
"vendor/", "tests/testdata/", "build/",
|
||||||
// don't relicense vendored sources
|
// don't relicense vendored sources
|
||||||
"crypto/sha3/", "crypto/ecies/", "log/",
|
"cmd/internal/browser",
|
||||||
|
"consensus/ethash/xor.go",
|
||||||
|
"crypto/bn256/",
|
||||||
|
"crypto/ecies/",
|
||||||
"crypto/secp256k1/curve.go",
|
"crypto/secp256k1/curve.go",
|
||||||
|
"crypto/sha3/",
|
||||||
|
"internal/jsre/deps",
|
||||||
|
"log/",
|
||||||
// don't license generated files
|
// don't license generated files
|
||||||
"contracts/chequebook/contract/",
|
"contracts/chequebook/contract/",
|
||||||
"contracts/ens/contract/",
|
"contracts/ens/contract/",
|
||||||
"contracts/release/contract.go",
|
"contracts/release/contract.go",
|
||||||
"p2p/discv5/nodeevent_string.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.
|
||||||
|
|
@ -284,6 +289,9 @@ func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) {
|
||||||
if !stat.Mode().IsRegular() {
|
if !stat.Mode().IsRegular() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if isGenerated(file) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
info, err := fileInfo(file)
|
info, err := fileInfo(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR %s: %v\n", file, err)
|
fmt.Printf("ERROR %s: %v\n", file, err)
|
||||||
|
|
@ -294,6 +302,23 @@ func getInfo(files <-chan string, out chan<- *info, wg *sync.WaitGroup) {
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isGenerated(file string) bool {
|
||||||
|
fd, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer fd.Close()
|
||||||
|
buf := make([]byte, 2048)
|
||||||
|
n, _ := fd.Read(buf)
|
||||||
|
buf = buf[:n]
|
||||||
|
for _, l := range bytes.Split(buf, []byte("\n")) {
|
||||||
|
if bytes.HasPrefix(l, []byte("// Code generated")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// fileInfo finds the lowest year in which the given file was committed.
|
// fileInfo finds the lowest year in which the given file was committed.
|
||||||
func fileInfo(file string) (*info, error) {
|
func fileInfo(file string) (*info, error) {
|
||||||
info := &info{file: file, Year: int64(time.Now().Year())}
|
info := &info{file: file, Year: int64(time.Now().Year())}
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ func main() {
|
||||||
if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil {
|
if err = crypto.SaveECDSA(*genKey, nodeKey); err != nil {
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
case *nodeKeyFile == "" && *nodeKeyHex == "":
|
case *nodeKeyFile == "" && *nodeKeyHex == "":
|
||||||
utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key")
|
utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key")
|
||||||
case *nodeKeyFile != "" && *nodeKeyHex != "":
|
case *nodeKeyFile != "" && *nodeKeyHex != "":
|
||||||
|
|
|
||||||
67
cmd/evm/json_logger.go
Normal file
67
cmd/evm/json_logger.go
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JSONLogger struct {
|
||||||
|
encoder *json.Encoder
|
||||||
|
cfg *vm.LogConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
|
||||||
|
return &JSONLogger{json.NewEncoder(writer), cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureState outputs state information on the logger.
|
||||||
|
func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
|
||||||
|
log := vm.StructLog{
|
||||||
|
Pc: pc,
|
||||||
|
Op: op,
|
||||||
|
Gas: gas + cost,
|
||||||
|
GasCost: cost,
|
||||||
|
MemorySize: memory.Len(),
|
||||||
|
Storage: nil,
|
||||||
|
Depth: depth,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
if !l.cfg.DisableMemory {
|
||||||
|
log.Memory = memory.Data()
|
||||||
|
}
|
||||||
|
if !l.cfg.DisableStack {
|
||||||
|
log.Stack = stack.Data()
|
||||||
|
}
|
||||||
|
return l.encoder.Encode(log)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureEnd is triggered at end of execution.
|
||||||
|
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration) error {
|
||||||
|
type endLog struct {
|
||||||
|
Output string `json:"output"`
|
||||||
|
GasUsed math.HexOrDecimal64 `json:"gasUsed"`
|
||||||
|
Time time.Duration `json:"time"`
|
||||||
|
}
|
||||||
|
return l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), t})
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,18 @@ var (
|
||||||
Name: "debug",
|
Name: "debug",
|
||||||
Usage: "output full trace logs",
|
Usage: "output full trace logs",
|
||||||
}
|
}
|
||||||
|
MemProfileFlag = cli.StringFlag{
|
||||||
|
Name: "memprofile",
|
||||||
|
Usage: "creates a memory profile at the given path",
|
||||||
|
}
|
||||||
|
CPUProfileFlag = cli.StringFlag{
|
||||||
|
Name: "cpuprofile",
|
||||||
|
Usage: "creates a CPU profile at the given path",
|
||||||
|
}
|
||||||
|
StatDumpFlag = cli.BoolFlag{
|
||||||
|
Name: "statdump",
|
||||||
|
Usage: "displays stack and heap memory information",
|
||||||
|
}
|
||||||
CodeFlag = cli.StringFlag{
|
CodeFlag = cli.StringFlag{
|
||||||
Name: "code",
|
Name: "code",
|
||||||
Usage: "EVM code",
|
Usage: "EVM code",
|
||||||
|
|
@ -78,6 +90,26 @@ var (
|
||||||
Name: "nogasmetering",
|
Name: "nogasmetering",
|
||||||
Usage: "disable gas metering",
|
Usage: "disable gas metering",
|
||||||
}
|
}
|
||||||
|
GenesisFlag = cli.StringFlag{
|
||||||
|
Name: "prestate",
|
||||||
|
Usage: "JSON file with prestate (genesis) config",
|
||||||
|
}
|
||||||
|
MachineFlag = cli.BoolFlag{
|
||||||
|
Name: "json",
|
||||||
|
Usage: "output trace logs in machine readable format (json)",
|
||||||
|
}
|
||||||
|
SenderFlag = cli.StringFlag{
|
||||||
|
Name: "sender",
|
||||||
|
Usage: "The transaction origin",
|
||||||
|
}
|
||||||
|
DisableMemoryFlag = cli.BoolFlag{
|
||||||
|
Name: "nomemory",
|
||||||
|
Usage: "disable memory output",
|
||||||
|
}
|
||||||
|
DisableStackFlag = cli.BoolFlag{
|
||||||
|
Name: "nostack",
|
||||||
|
Usage: "disable stack output",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -93,6 +125,14 @@ func init() {
|
||||||
DumpFlag,
|
DumpFlag,
|
||||||
InputFlag,
|
InputFlag,
|
||||||
DisableGasMeteringFlag,
|
DisableGasMeteringFlag,
|
||||||
|
MemProfileFlag,
|
||||||
|
CPUProfileFlag,
|
||||||
|
StatDumpFlag,
|
||||||
|
GenesisFlag,
|
||||||
|
MachineFlag,
|
||||||
|
SenderFlag,
|
||||||
|
DisableMemoryFlag,
|
||||||
|
DisableStackFlag,
|
||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
compileCommand,
|
compileCommand,
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,11 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime/pprof"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
|
|
@ -28,11 +30,13 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
|
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/core/vm/runtime"
|
"github.com/ethereum/go-ethereum/core/vm/runtime"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
cli "gopkg.in/urfave/cli.v1"
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,17 +48,63 @@ var runCommand = cli.Command{
|
||||||
Description: `The run command runs arbitrary EVM code.`,
|
Description: `The run command runs arbitrary EVM code.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readGenesis will read the given JSON format genesis file and return
|
||||||
|
// the initialized Genesis structure
|
||||||
|
func readGenesis(genesisPath string) *core.Genesis {
|
||||||
|
// Make sure we have a valid genesis JSON
|
||||||
|
//genesisPath := ctx.Args().First()
|
||||||
|
if len(genesisPath) == 0 {
|
||||||
|
utils.Fatalf("Must supply path to genesis JSON file")
|
||||||
|
}
|
||||||
|
file, err := os.Open(genesisPath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read genesis file: %v", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
genesis := new(core.Genesis)
|
||||||
|
if err := json.NewDecoder(file).Decode(genesis); err != nil {
|
||||||
|
utils.Fatalf("invalid genesis file: %v", err)
|
||||||
|
}
|
||||||
|
return genesis
|
||||||
|
}
|
||||||
|
|
||||||
func runCmd(ctx *cli.Context) error {
|
func runCmd(ctx *cli.Context) error {
|
||||||
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
|
||||||
glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
|
glogger.Verbosity(log.Lvl(ctx.GlobalInt(VerbosityFlag.Name)))
|
||||||
log.Root().SetHandler(glogger)
|
log.Root().SetHandler(glogger)
|
||||||
|
logconfig := &vm.LogConfig{
|
||||||
|
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||||
|
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
db, _ = ethdb.NewMemDatabase()
|
tracer vm.Tracer
|
||||||
statedb, _ = state.New(common.Hash{}, db)
|
debugLogger *vm.StructLogger
|
||||||
|
statedb *state.StateDB
|
||||||
|
chainConfig *params.ChainConfig
|
||||||
sender = common.StringToAddress("sender")
|
sender = common.StringToAddress("sender")
|
||||||
logger = vm.NewStructLogger(nil)
|
|
||||||
)
|
)
|
||||||
|
if ctx.GlobalBool(MachineFlag.Name) {
|
||||||
|
tracer = NewJSONLogger(logconfig, os.Stdout)
|
||||||
|
} else if ctx.GlobalBool(DebugFlag.Name) {
|
||||||
|
debugLogger = vm.NewStructLogger(logconfig)
|
||||||
|
tracer = debugLogger
|
||||||
|
} else {
|
||||||
|
debugLogger = vm.NewStructLogger(logconfig)
|
||||||
|
}
|
||||||
|
if ctx.GlobalString(GenesisFlag.Name) != "" {
|
||||||
|
gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
|
||||||
|
_, statedb = gen.ToBlock()
|
||||||
|
chainConfig = gen.Config
|
||||||
|
} else {
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
statedb, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
}
|
||||||
|
if ctx.GlobalString(SenderFlag.Name) != "" {
|
||||||
|
sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))
|
||||||
|
}
|
||||||
|
|
||||||
statedb.CreateAccount(sender)
|
statedb.CreateAccount(sender)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -94,43 +144,77 @@ func runCmd(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n")))
|
code = common.Hex2Bytes(string(bytes.TrimRight(hexcode, "\n")))
|
||||||
}
|
}
|
||||||
|
initialGas := ctx.GlobalUint64(GasFlag.Name)
|
||||||
runtimeConfig := runtime.Config{
|
runtimeConfig := runtime.Config{
|
||||||
Origin: sender,
|
Origin: sender,
|
||||||
State: statedb,
|
State: statedb,
|
||||||
GasLimit: ctx.GlobalUint64(GasFlag.Name),
|
GasLimit: initialGas,
|
||||||
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
|
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
|
||||||
Value: utils.GlobalBig(ctx, ValueFlag.Name),
|
Value: utils.GlobalBig(ctx, ValueFlag.Name),
|
||||||
EVMConfig: vm.Config{
|
EVMConfig: vm.Config{
|
||||||
Tracer: logger,
|
Tracer: tracer,
|
||||||
Debug: ctx.GlobalBool(DebugFlag.Name),
|
Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name),
|
||||||
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
|
DisableGasMetering: ctx.GlobalBool(DisableGasMeteringFlag.Name),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cpuProfilePath := ctx.GlobalString(CPUProfileFlag.Name); cpuProfilePath != "" {
|
||||||
|
f, err := os.Create(cpuProfilePath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("could not create CPU profile: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
|
fmt.Println("could not start CPU profile: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
if chainConfig != nil {
|
||||||
|
runtimeConfig.ChainConfig = chainConfig
|
||||||
|
}
|
||||||
tstart := time.Now()
|
tstart := time.Now()
|
||||||
|
var leftOverGas uint64
|
||||||
if ctx.GlobalBool(CreateFlag.Name) {
|
if ctx.GlobalBool(CreateFlag.Name) {
|
||||||
input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
|
input := append(code, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
|
||||||
ret, _, err = runtime.Create(input, &runtimeConfig)
|
ret, _, leftOverGas, err = runtime.Create(input, &runtimeConfig)
|
||||||
} else {
|
} else {
|
||||||
receiver := common.StringToAddress("receiver")
|
receiver := common.StringToAddress("receiver")
|
||||||
statedb.SetCode(receiver, code)
|
statedb.SetCode(receiver, code)
|
||||||
|
|
||||||
ret, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig)
|
ret, leftOverGas, err = runtime.Call(receiver, common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)), &runtimeConfig)
|
||||||
}
|
}
|
||||||
execTime := time.Since(tstart)
|
execTime := time.Since(tstart)
|
||||||
|
|
||||||
if ctx.GlobalBool(DumpFlag.Name) {
|
if ctx.GlobalBool(DumpFlag.Name) {
|
||||||
statedb.Commit(true)
|
statedb.IntermediateRoot(true)
|
||||||
fmt.Println(string(statedb.Dump()))
|
fmt.Println(string(statedb.Dump()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if memProfilePath := ctx.GlobalString(MemProfileFlag.Name); memProfilePath != "" {
|
||||||
|
f, err := os.Create(memProfilePath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("could not create memory profile: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
|
fmt.Println("could not write memory profile: ", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.GlobalBool(DebugFlag.Name) {
|
if ctx.GlobalBool(DebugFlag.Name) {
|
||||||
|
if debugLogger != nil {
|
||||||
fmt.Fprintln(os.Stderr, "#### TRACE ####")
|
fmt.Fprintln(os.Stderr, "#### TRACE ####")
|
||||||
vm.WriteTrace(os.Stderr, logger.StructLogs())
|
vm.WriteTrace(os.Stderr, debugLogger.StructLogs())
|
||||||
|
}
|
||||||
fmt.Fprintln(os.Stderr, "#### LOGS ####")
|
fmt.Fprintln(os.Stderr, "#### LOGS ####")
|
||||||
vm.WriteLogs(os.Stderr, statedb.Logs())
|
vm.WriteLogs(os.Stderr, statedb.Logs())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalBool(StatDumpFlag.Name) {
|
||||||
var mem goruntime.MemStats
|
var mem goruntime.MemStats
|
||||||
goruntime.ReadMemStats(&mem)
|
goruntime.ReadMemStats(&mem)
|
||||||
fmt.Fprintf(os.Stderr, `evm execution time: %v
|
fmt.Fprintf(os.Stderr, `evm execution time: %v
|
||||||
|
|
@ -138,14 +222,18 @@ heap objects: %d
|
||||||
allocations: %d
|
allocations: %d
|
||||||
total allocations: %d
|
total allocations: %d
|
||||||
GC calls: %d
|
GC calls: %d
|
||||||
|
Gas used: %d
|
||||||
|
|
||||||
`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC)
|
`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
|
||||||
|
}
|
||||||
|
if tracer != nil {
|
||||||
|
tracer.CaptureEnd(ret, initialGas-leftOverGas, execTime)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("0x%x\n", ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("0x%x", ret)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" error: %v", err)
|
fmt.Printf(" error: %v\n", err)
|
||||||
}
|
}
|
||||||
fmt.Println()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
544
cmd/faucet/faucet.go
Normal file
544
cmd/faucet/faucet.go
Normal file
|
|
@ -0,0 +1,544 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// faucet is a Ether faucet backed by a light client.
|
||||||
|
package main
|
||||||
|
|
||||||
|
//go:generate go-bindata -nometadata -o website.go faucet.html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
"github.com/ethereum/go-ethereum/ethstats"
|
||||||
|
"github.com/ethereum/go-ethereum/les"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"golang.org/x/net/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
genesisFlag = flag.String("genesis", "", "Genesis json file to seed the chain with")
|
||||||
|
apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection")
|
||||||
|
ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection")
|
||||||
|
bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with")
|
||||||
|
netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol")
|
||||||
|
statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string")
|
||||||
|
|
||||||
|
netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet")
|
||||||
|
payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request")
|
||||||
|
minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds")
|
||||||
|
tiersFlag = flag.Int("faucet.tiers", 3, "Number of funding tiers to enable (x3 time, x2.5 funds)")
|
||||||
|
|
||||||
|
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
|
||||||
|
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
|
||||||
|
|
||||||
|
githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access")
|
||||||
|
githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with")
|
||||||
|
|
||||||
|
captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
|
||||||
|
captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
|
||||||
|
|
||||||
|
logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ether = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Parse the flags and set up the logger to print everything requested
|
||||||
|
flag.Parse()
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
|
|
||||||
|
// Construct the payout tiers
|
||||||
|
amounts := make([]string, *tiersFlag)
|
||||||
|
periods := make([]string, *tiersFlag)
|
||||||
|
for i := 0; i < *tiersFlag; i++ {
|
||||||
|
// Calculate the amount for the next tier and format it
|
||||||
|
amount := float64(*payoutFlag) * math.Pow(2.5, float64(i))
|
||||||
|
amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64))
|
||||||
|
if amount == 1 {
|
||||||
|
amounts[i] = strings.TrimSuffix(amounts[i], "s")
|
||||||
|
}
|
||||||
|
// Calculate the period for the next tier and format it
|
||||||
|
period := *minutesFlag * int(math.Pow(3, float64(i)))
|
||||||
|
periods[i] = fmt.Sprintf("%d mins", period)
|
||||||
|
if period%60 == 0 {
|
||||||
|
period /= 60
|
||||||
|
periods[i] = fmt.Sprintf("%d hours", period)
|
||||||
|
|
||||||
|
if period%24 == 0 {
|
||||||
|
period /= 24
|
||||||
|
periods[i] = fmt.Sprintf("%d days", period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if period == 1 {
|
||||||
|
periods[i] = strings.TrimSuffix(periods[i], "s")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load up and render the faucet website
|
||||||
|
tmpl, err := Asset("faucet.html")
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to load the faucet template", "err", err)
|
||||||
|
}
|
||||||
|
website := new(bytes.Buffer)
|
||||||
|
err = template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
|
||||||
|
"Network": *netnameFlag,
|
||||||
|
"Amounts": amounts,
|
||||||
|
"Periods": periods,
|
||||||
|
"Recaptcha": *captchaToken,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to render the faucet template", "err", err)
|
||||||
|
}
|
||||||
|
// Load and parse the genesis block requested by the user
|
||||||
|
blob, err := ioutil.ReadFile(*genesisFlag)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read genesis block contents", "genesis", *genesisFlag, "err", err)
|
||||||
|
}
|
||||||
|
genesis := new(core.Genesis)
|
||||||
|
if err = json.Unmarshal(blob, genesis); err != nil {
|
||||||
|
log.Crit("Failed to parse genesis block json", "err", err)
|
||||||
|
}
|
||||||
|
// Convert the bootnodes to internal enode representations
|
||||||
|
var enodes []*discv5.Node
|
||||||
|
for _, boot := range strings.Split(*bootFlag, ",") {
|
||||||
|
if url, err := discv5.ParseNode(boot); err == nil {
|
||||||
|
enodes = append(enodes, url)
|
||||||
|
} else {
|
||||||
|
log.Error("Failed to parse bootnode URL", "url", boot, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load up the account key and decrypt its password
|
||||||
|
if blob, err = ioutil.ReadFile(*accPassFlag); err != nil {
|
||||||
|
log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err)
|
||||||
|
}
|
||||||
|
pass := string(blob)
|
||||||
|
|
||||||
|
ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
|
if blob, err = ioutil.ReadFile(*accJSONFlag); err != nil {
|
||||||
|
log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err)
|
||||||
|
}
|
||||||
|
acc, err := ks.Import(blob, pass, pass)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to import faucet signer account", "err", err)
|
||||||
|
}
|
||||||
|
ks.Unlock(acc, pass)
|
||||||
|
|
||||||
|
// Assemble and start the faucet light service
|
||||||
|
faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to start faucet", "err", err)
|
||||||
|
}
|
||||||
|
defer faucet.close()
|
||||||
|
|
||||||
|
if err := faucet.listenAndServe(*apiPortFlag); err != nil {
|
||||||
|
log.Crit("Failed to launch faucet API", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// request represents an accepted funding request.
|
||||||
|
type request struct {
|
||||||
|
Username string `json:"username"` // GitHub user for displaying an avatar
|
||||||
|
Account common.Address `json:"account"` // Ethereum address being funded
|
||||||
|
Time time.Time `json:"time"` // Timestamp when te request was accepted
|
||||||
|
Tx *types.Transaction `json:"tx"` // Transaction funding the account
|
||||||
|
}
|
||||||
|
|
||||||
|
// faucet represents a crypto faucet backed by an Ethereum light client.
|
||||||
|
type faucet struct {
|
||||||
|
config *params.ChainConfig // Chain configurations for signing
|
||||||
|
stack *node.Node // Ethereum protocol stack
|
||||||
|
client *ethclient.Client // Client connection to the Ethereum chain
|
||||||
|
index []byte // Index page to serve up on the web
|
||||||
|
|
||||||
|
keystore *keystore.KeyStore // Keystore containing the single signer
|
||||||
|
account accounts.Account // Account funding user faucet requests
|
||||||
|
nonce uint64 // Current pending nonce of the faucet
|
||||||
|
price *big.Int // Current gas price to issue funds with
|
||||||
|
|
||||||
|
conns []*websocket.Conn // Currently live websocket connections
|
||||||
|
timeouts map[string]time.Time // History of users and their funding timeouts
|
||||||
|
reqs []*request // Currently pending funding requests
|
||||||
|
update chan struct{} // Channel to signal request updates
|
||||||
|
|
||||||
|
lock sync.RWMutex // Lock protecting the faucet's internals
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
|
||||||
|
// Assemble the raw devp2p protocol stack
|
||||||
|
stack, err := node.New(&node.Config{
|
||||||
|
Name: "geth",
|
||||||
|
Version: params.Version,
|
||||||
|
DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
|
||||||
|
P2P: p2p.Config{
|
||||||
|
NAT: nat.Any(),
|
||||||
|
NoDiscovery: true,
|
||||||
|
DiscoveryV5: true,
|
||||||
|
ListenAddr: fmt.Sprintf(":%d", port),
|
||||||
|
DiscoveryV5Addr: fmt.Sprintf(":%d", port+1),
|
||||||
|
MaxPeers: 25,
|
||||||
|
BootstrapNodesV5: enodes,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Assemble the Ethereum light client protocol
|
||||||
|
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
|
cfg := eth.DefaultConfig
|
||||||
|
cfg.SyncMode = downloader.LightSync
|
||||||
|
cfg.NetworkId = network
|
||||||
|
cfg.Genesis = genesis
|
||||||
|
return les.New(ctx, &cfg)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Assemble the ethstats monitoring and reporting service'
|
||||||
|
if stats != "" {
|
||||||
|
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
|
var serv *les.LightEthereum
|
||||||
|
ctx.Service(&serv)
|
||||||
|
return ethstats.New(stats, nil, serv)
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Boot up the client and ensure it connects to bootnodes
|
||||||
|
if err := stack.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, boot := range enodes {
|
||||||
|
old, _ := discover.ParseNode(boot.String())
|
||||||
|
stack.Server().AddPeer(old)
|
||||||
|
}
|
||||||
|
// Attach to the client and retrieve and interesting metadatas
|
||||||
|
api, err := stack.Attach()
|
||||||
|
if err != nil {
|
||||||
|
stack.Stop()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := ethclient.NewClient(api)
|
||||||
|
|
||||||
|
return &faucet{
|
||||||
|
config: genesis.Config,
|
||||||
|
stack: stack,
|
||||||
|
client: client,
|
||||||
|
index: index,
|
||||||
|
keystore: ks,
|
||||||
|
account: ks.Accounts()[0],
|
||||||
|
timeouts: make(map[string]time.Time),
|
||||||
|
update: make(chan struct{}, 1),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// close terminates the Ethereum connection and tears down the faucet.
|
||||||
|
func (f *faucet) close() error {
|
||||||
|
return f.stack.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// listenAndServe registers the HTTP handlers for the faucet and boots it up
|
||||||
|
// for service user funding requests.
|
||||||
|
func (f *faucet) listenAndServe(port int) error {
|
||||||
|
go f.loop()
|
||||||
|
|
||||||
|
http.HandleFunc("/", f.webHandler)
|
||||||
|
http.Handle("/api", websocket.Handler(f.apiHandler))
|
||||||
|
|
||||||
|
return http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// webHandler handles all non-api requests, simply flattening and returning the
|
||||||
|
// faucet website.
|
||||||
|
func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(f.index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apiHandler handles requests for Ether grants and transaction statuses.
|
||||||
|
func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
|
// Start tracking the connection and drop at the end
|
||||||
|
f.lock.Lock()
|
||||||
|
f.conns = append(f.conns, conn)
|
||||||
|
f.lock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
f.lock.Lock()
|
||||||
|
for i, c := range f.conns {
|
||||||
|
if c == conn {
|
||||||
|
f.conns = append(f.conns[:i], f.conns[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.lock.Unlock()
|
||||||
|
}()
|
||||||
|
// Send a few initial stats to the client
|
||||||
|
balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
|
||||||
|
nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
|
||||||
|
|
||||||
|
websocket.JSON.Send(conn, map[string]interface{}{
|
||||||
|
"funds": balance.Div(balance, ether),
|
||||||
|
"funded": nonce,
|
||||||
|
"peers": f.stack.Server().PeerCount(),
|
||||||
|
"requests": f.reqs,
|
||||||
|
})
|
||||||
|
// Send the initial block to the client
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
header, err := f.client.HeaderByNumber(ctx, nil)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to retrieve latest header", "err", err)
|
||||||
|
} else {
|
||||||
|
websocket.JSON.Send(conn, header)
|
||||||
|
}
|
||||||
|
// Keep reading requests from the websocket until the connection breaks
|
||||||
|
for {
|
||||||
|
// Fetch the next funding request and validate against github
|
||||||
|
var msg struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Tier uint `json:"tier"`
|
||||||
|
Captcha string `json:"captcha"`
|
||||||
|
}
|
||||||
|
if err := websocket.JSON.Receive(conn, &msg); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(msg.URL, "https://gist.github.com/") {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if msg.Tier >= uint(*tiersFlag) {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "Invalid funding tier requested"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info("Faucet funds requested", "gist", msg.URL, "tier", msg.Tier)
|
||||||
|
|
||||||
|
// If captcha verifications are enabled, make sure we're not dealing with a robot
|
||||||
|
if *captchaToken != "" {
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("secret", *captchaSecret)
|
||||||
|
form.Add("response", msg.Captcha)
|
||||||
|
|
||||||
|
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
|
||||||
|
if err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var result struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Errors json.RawMessage `json:"error-codes"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&result)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !result.Success {
|
||||||
|
log.Warn("Captcha verification failed", "err", string(result.Errors))
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "Beep-bop, you're a robot!"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Retrieve the gist from the GitHub Gist APIs
|
||||||
|
parts := strings.Split(msg.URL, "/")
|
||||||
|
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
|
||||||
|
if *githubUser != "" {
|
||||||
|
req.SetBasicAuth(*githubUser, *githubToken)
|
||||||
|
}
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var gist struct {
|
||||||
|
Owner struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
} `json:"owner"`
|
||||||
|
Files map[string]struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
} `json:"files"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&gist)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if gist.Owner.Login == "" {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "Anonymous Gists not allowed"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Iterate over all the files and look for Ethereum addresses
|
||||||
|
var address common.Address
|
||||||
|
for _, file := range gist.Files {
|
||||||
|
content := strings.TrimSpace(file.Content)
|
||||||
|
if len(content) == 2+common.AddressLength*2 {
|
||||||
|
address = common.HexToAddress(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if address == (common.Address{}) {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "No Ethereum address found to fund"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Validate the user's existence since the API is unhelpful here
|
||||||
|
if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "Invalid user... boom!"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Ensure the user didn't request funds too recently
|
||||||
|
f.lock.Lock()
|
||||||
|
var (
|
||||||
|
fund bool
|
||||||
|
timeout time.Time
|
||||||
|
)
|
||||||
|
if timeout = f.timeouts[gist.Owner.Login]; time.Now().After(timeout) {
|
||||||
|
// User wasn't funded recently, create the funding transaction
|
||||||
|
amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether)
|
||||||
|
amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil))
|
||||||
|
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
|
||||||
|
|
||||||
|
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, big.NewInt(21000), f.price, nil)
|
||||||
|
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
||||||
|
if err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
f.lock.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Submit the transaction and mark as funded if successful
|
||||||
|
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
|
f.lock.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.reqs = append(f.reqs, &request{
|
||||||
|
Username: gist.Owner.Login,
|
||||||
|
Account: address,
|
||||||
|
Time: time.Now(),
|
||||||
|
Tx: signed,
|
||||||
|
})
|
||||||
|
f.timeouts[gist.Owner.Login] = time.Now().Add(time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute)
|
||||||
|
fund = true
|
||||||
|
}
|
||||||
|
f.lock.Unlock()
|
||||||
|
|
||||||
|
// Send an error if too frequent funding, othewise a success
|
||||||
|
if !fund {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", gist.Owner.Login, address.Hex())})
|
||||||
|
select {
|
||||||
|
case f.update <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop keeps waiting for interesting events and pushes them out to connected
|
||||||
|
// websockets.
|
||||||
|
func (f *faucet) loop() {
|
||||||
|
// Wait for chain events and push them to clients
|
||||||
|
heads := make(chan *types.Header, 16)
|
||||||
|
sub, err := f.client.SubscribeNewHead(context.Background(), heads)
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to subscribe to head events", "err", err)
|
||||||
|
}
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case head := <-heads:
|
||||||
|
// New chain head arrived, query the current stats and stream to clients
|
||||||
|
balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
|
||||||
|
balance = new(big.Int).Div(balance, ether)
|
||||||
|
|
||||||
|
price, _ := f.client.SuggestGasPrice(context.Background())
|
||||||
|
nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
|
||||||
|
|
||||||
|
f.lock.Lock()
|
||||||
|
f.price, f.nonce = price, nonce
|
||||||
|
for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce {
|
||||||
|
f.reqs = f.reqs[1:]
|
||||||
|
}
|
||||||
|
f.lock.Unlock()
|
||||||
|
|
||||||
|
f.lock.RLock()
|
||||||
|
for _, conn := range f.conns {
|
||||||
|
if err := websocket.JSON.Send(conn, map[string]interface{}{
|
||||||
|
"funds": balance,
|
||||||
|
"funded": f.nonce,
|
||||||
|
"peers": f.stack.Server().PeerCount(),
|
||||||
|
"requests": f.reqs,
|
||||||
|
}); err != nil {
|
||||||
|
log.Warn("Failed to send stats to client", "err", err)
|
||||||
|
conn.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := websocket.JSON.Send(conn, head); err != nil {
|
||||||
|
log.Warn("Failed to send header to client", "err", err)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.lock.RUnlock()
|
||||||
|
|
||||||
|
case <-f.update:
|
||||||
|
// Pending requests updated, stream to clients
|
||||||
|
f.lock.RLock()
|
||||||
|
for _, conn := range f.conns {
|
||||||
|
if err := websocket.JSON.Send(conn, map[string]interface{}{"requests": f.reqs}); err != nil {
|
||||||
|
log.Warn("Failed to send requests to client", "err", err)
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.lock.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
cmd/faucet/faucet.html
Normal file
150
cmd/faucet/faucet.html
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>{{.Network}}: GitHub Faucet</title>
|
||||||
|
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-noty/2.4.1/packaged/jquery.noty.packaged.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.vertical-center {
|
||||||
|
min-height: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.progress span {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
padding: 6px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="vertical-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row" style="margin-bottom: 16px;">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 style="text-align: center;"><i class="fa fa-bath" aria-hidden="true"></i> {{.Network}} GitHub Authenticated Faucet <i class="fa fa-github-alt" aria-hidden="true"></i></h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 col-lg-offset-2">
|
||||||
|
<div class="input-group">
|
||||||
|
<input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address...">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Give me Ether <i class="fa fa-caret-down" aria-hidden="true"></i></button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-right">{{range $idx, $amount := .Amounts}}
|
||||||
|
<li><a style="text-align: center;" onclick="tier={{$idx}}; {{if $.Recaptcha}}grecaptcha.execute(){{else}}submit({{$idx}}){{end}}">{{$amount}} / {{index $.Periods $idx}}</a></li>{{end}}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</div>{{if .Recaptcha}}
|
||||||
|
<div class="g-recaptcha" data-sitekey="{{.Recaptcha}}" data-callback="submit" data-size="invisible"></div>{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top: 32px;">
|
||||||
|
<div class="col-lg-6 col-lg-offset-3">
|
||||||
|
<div class="panel panel-small panel-default">
|
||||||
|
<div class="panel-body" style="padding: 0; overflow: auto; max-height: 300px;">
|
||||||
|
<table id="requests" class="table table-condensed" style="margin: 0;"></table>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<table style="width: 100%"><tr>
|
||||||
|
<td style="text-align: center;"><i class="fa fa-rss" aria-hidden="true"></i> <span id="peers"></span> peers</td>
|
||||||
|
<td style="text-align: center;"><i class="fa fa-database" aria-hidden="true"></i> <span id="block"></span> blocks</td>
|
||||||
|
<td style="text-align: center;"><i class="fa fa-heartbeat" aria-hidden="true"></i> <span id="funds"></span> Ethers</td>
|
||||||
|
<td style="text-align: center;"><i class="fa fa-university" aria-hidden="true"></i> <span id="funded"></span> funded</td>
|
||||||
|
</tr></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="margin-top: 32px;">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h3>How does this work?</h3>
|
||||||
|
<p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to GitHub accounts. Anyone having a GitHub account may request funds within the permitted limits.</p>
|
||||||
|
<p>To request funds, simply create a <a href="https://gist.github.com/" target="_about:blank">GitHub Gist</a> with your Ethereum address pasted into the contents (the file name doesn't matter), copy paste the gists URL into the above input box and fire away! You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
|
||||||
|
{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Global variables to hold the current status of the faucet
|
||||||
|
var attempt = 0;
|
||||||
|
var server;
|
||||||
|
var tier = 0;
|
||||||
|
|
||||||
|
// Define the function that submits a gist url to the server
|
||||||
|
var submit = function({{if .Recaptcha}}captcha{{end}}) {
|
||||||
|
server.send(JSON.stringify({url: $("#gist")[0].value, tier: tier{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
|
||||||
|
grecaptcha.reset();{{end}}
|
||||||
|
};
|
||||||
|
// Define a method to reconnect upon server loss
|
||||||
|
var reconnect = function() {
|
||||||
|
if (attempt % 2 == 0) {
|
||||||
|
server = new WebSocket("wss://" + location.host + "/api");
|
||||||
|
} else {
|
||||||
|
server = new WebSocket("ws://" + location.host + "/api");
|
||||||
|
}
|
||||||
|
attempt++;
|
||||||
|
|
||||||
|
server.onmessage = function(event) {
|
||||||
|
var msg = JSON.parse(event.data);
|
||||||
|
if (msg === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.funds !== undefined) {
|
||||||
|
$("#funds").text(msg.funds);
|
||||||
|
}
|
||||||
|
if (msg.funded !== undefined) {
|
||||||
|
$("#funded").text(msg.funded);
|
||||||
|
}
|
||||||
|
if (msg.peers !== undefined) {
|
||||||
|
$("#peers").text(msg.peers);
|
||||||
|
}
|
||||||
|
if (msg.number !== undefined) {
|
||||||
|
$("#block").text(parseInt(msg.number, 16));
|
||||||
|
}
|
||||||
|
if (msg.error !== undefined) {
|
||||||
|
noty({layout: 'topCenter', text: msg.error, type: 'error'});
|
||||||
|
}
|
||||||
|
if (msg.success !== undefined) {
|
||||||
|
noty({layout: 'topCenter', text: msg.success, type: 'success'});
|
||||||
|
}
|
||||||
|
if (msg.requests !== undefined && msg.requests !== null) {
|
||||||
|
var content = "";
|
||||||
|
for (var i=0; i<msg.requests.length; i++) {
|
||||||
|
content += "<tr><td><div style=\"background: url('https://github.com/" + msg.requests[i].username + ".png?size=64'); background-size: cover; width:32px; height: 32px; border-radius: 4px;\"></div></td><td><pre>" + msg.requests[i].account + "</pre></td><td style=\"width: 100%; text-align: center; vertical-align: middle;\">" + moment.duration(moment(msg.requests[i].time).unix()-moment().unix(), 'seconds').humanize(true) + "</td></tr>";
|
||||||
|
}
|
||||||
|
$("#requests").html("<tbody>" + content + "</tbody>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.onclose = function() { setTimeout(reconnect, 3000); };
|
||||||
|
}
|
||||||
|
// Establish a websocket connection to the API server
|
||||||
|
reconnect();
|
||||||
|
</script>{{if .Recaptcha}}
|
||||||
|
<script src="https://www.google.com/recaptcha/api.js" async defer></script>{{end}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
235
cmd/faucet/website.go
Normal file
235
cmd/faucet/website.go
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -40,32 +40,39 @@ var (
|
||||||
|
|
||||||
will prompt for your password and imports your ether presale account.
|
will prompt for your password and imports your ether presale account.
|
||||||
It can be used non-interactively with the --password option taking a
|
It can be used non-interactively with the --password option taking a
|
||||||
passwordfile as argument containing the wallet password in plaintext.
|
passwordfile as argument containing the wallet password in plaintext.`,
|
||||||
|
|
||||||
`,
|
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
{
|
{
|
||||||
Action: importWallet,
|
|
||||||
Name: "import",
|
Name: "import",
|
||||||
Usage: "Import Ethereum presale wallet",
|
Usage: "Import Ethereum presale wallet",
|
||||||
ArgsUsage: "<keyFile>",
|
ArgsUsage: "<keyFile>",
|
||||||
|
Action: utils.MigrateFlags(importWallet),
|
||||||
|
Category: "ACCOUNT COMMANDS",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
utils.LightKDFFlag,
|
||||||
|
},
|
||||||
Description: `
|
Description: `
|
||||||
TODO: Please write this
|
geth wallet [options] /path/to/my/presale.wallet
|
||||||
`,
|
|
||||||
|
will prompt for your password and imports your ether presale account.
|
||||||
|
It can be used non-interactively with the --password option taking a
|
||||||
|
passwordfile as argument containing the wallet password in plaintext.`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
accountCommand = cli.Command{
|
accountCommand = cli.Command{
|
||||||
Action: accountList,
|
|
||||||
Name: "account",
|
Name: "account",
|
||||||
Usage: "Manage accounts",
|
Usage: "Manage accounts",
|
||||||
ArgsUsage: "",
|
|
||||||
Category: "ACCOUNT COMMANDS",
|
Category: "ACCOUNT COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
Manage accounts lets you create new accounts, list all existing accounts,
|
|
||||||
import a private key into a new account.
|
|
||||||
|
|
||||||
' help' shows a list of subcommands or help for one subcommand.
|
Manage accounts, list all existing accounts, import a private key into a new
|
||||||
|
account, create a new account or update an existing account.
|
||||||
|
|
||||||
It supports interactive mode, when you are prompted for password as well as
|
It supports interactive mode, when you are prompted for password as well as
|
||||||
non-interactive mode where passwords are supplied via a given password file.
|
non-interactive mode where passwords are supplied via a given password file.
|
||||||
|
|
@ -80,36 +87,34 @@ Note that exporting your key in unencrypted format is NOT supported.
|
||||||
Keys are stored under <DATADIR>/keystore.
|
Keys are stored under <DATADIR>/keystore.
|
||||||
It is safe to transfer the entire directory or the individual keys therein
|
It is safe to transfer the entire directory or the individual keys therein
|
||||||
between ethereum nodes by simply copying.
|
between ethereum nodes by simply copying.
|
||||||
Make sure you backup your keys regularly.
|
|
||||||
|
|
||||||
In order to use your account to send transactions, you need to unlock them using
|
Make sure you backup your keys regularly.`,
|
||||||
the '--unlock' option. The argument is a space separated list of addresses or
|
|
||||||
indexes. If used non-interactively with a passwordfile, the file should contain
|
|
||||||
the respective passwords one per line. If you unlock n accounts and the password
|
|
||||||
file contains less than n entries, then the last password is meant to apply to
|
|
||||||
all remaining accounts.
|
|
||||||
|
|
||||||
And finally. DO NOT FORGET YOUR PASSWORD.
|
|
||||||
`,
|
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
{
|
{
|
||||||
Action: accountList,
|
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "Print account addresses",
|
Usage: "Print summary of existing accounts",
|
||||||
ArgsUsage: " ",
|
Action: utils.MigrateFlags(accountList),
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.KeyStoreDirFlag,
|
||||||
|
},
|
||||||
Description: `
|
Description: `
|
||||||
TODO: Please write this
|
Print a short summary of all accounts`,
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Action: accountCreate,
|
|
||||||
Name: "new",
|
Name: "new",
|
||||||
Usage: "Create a new account",
|
Usage: "Create a new account",
|
||||||
ArgsUsage: " ",
|
Action: utils.MigrateFlags(accountCreate),
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
utils.LightKDFFlag,
|
||||||
|
},
|
||||||
Description: `
|
Description: `
|
||||||
geth account new
|
geth account new
|
||||||
|
|
||||||
Creates a new account. Prints the address.
|
Creates a new account and prints the address.
|
||||||
|
|
||||||
The account is saved in encrypted format, you are prompted for a passphrase.
|
The account is saved in encrypted format, you are prompted for a passphrase.
|
||||||
|
|
||||||
|
|
@ -117,17 +122,20 @@ You must remember this passphrase to unlock your account in the future.
|
||||||
|
|
||||||
For non-interactive use the passphrase can be specified with the --password flag:
|
For non-interactive use the passphrase can be specified with the --password flag:
|
||||||
|
|
||||||
geth --password <passwordfile> account new
|
|
||||||
|
|
||||||
Note, this is meant to be used for testing only, it is a bad idea to save your
|
Note, this is meant to be used for testing only, it is a bad idea to save your
|
||||||
password to file or expose in any other way.
|
password to file or expose in any other way.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Action: accountUpdate,
|
|
||||||
Name: "update",
|
Name: "update",
|
||||||
Usage: "Update an existing account",
|
Usage: "Update an existing account",
|
||||||
|
Action: utils.MigrateFlags(accountUpdate),
|
||||||
ArgsUsage: "<address>",
|
ArgsUsage: "<address>",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.LightKDFFlag,
|
||||||
|
},
|
||||||
Description: `
|
Description: `
|
||||||
geth account update <address>
|
geth account update <address>
|
||||||
|
|
||||||
|
|
@ -141,16 +149,22 @@ format to the newest format or change the password for an account.
|
||||||
|
|
||||||
For non-interactive use the passphrase can be specified with the --password flag:
|
For non-interactive use the passphrase can be specified with the --password flag:
|
||||||
|
|
||||||
geth --password <passwordfile> account update <address>
|
geth account update [options] <address>
|
||||||
|
|
||||||
Since only one password can be given, only format update can be performed,
|
Since only one password can be given, only format update can be performed,
|
||||||
changing your password is only possible interactively.
|
changing your password is only possible interactively.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Action: accountImport,
|
|
||||||
Name: "import",
|
Name: "import",
|
||||||
Usage: "Import a private key into a new account",
|
Usage: "Import a private key into a new account",
|
||||||
|
Action: utils.MigrateFlags(accountImport),
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
utils.LightKDFFlag,
|
||||||
|
},
|
||||||
ArgsUsage: "<keyFile>",
|
ArgsUsage: "<keyFile>",
|
||||||
Description: `
|
Description: `
|
||||||
geth account import <keyfile>
|
geth account import <keyfile>
|
||||||
|
|
@ -166,7 +180,7 @@ You must remember this passphrase to unlock your account in the future.
|
||||||
|
|
||||||
For non-interactive use the passphrase can be specified with the -password flag:
|
For non-interactive use the passphrase can be specified with the -password flag:
|
||||||
|
|
||||||
geth --password <passwordfile> account import <keyfile>
|
geth account import [options] <keyfile>
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
As you can directly copy your encrypted accounts to another ethereum instance,
|
As you can directly copy your encrypted accounts to another ethereum instance,
|
||||||
|
|
@ -179,8 +193,7 @@ nodes.
|
||||||
)
|
)
|
||||||
|
|
||||||
func accountList(ctx *cli.Context) error {
|
func accountList(ctx *cli.Context) error {
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
stack, _ := makeConfigNode(ctx)
|
||||||
|
|
||||||
var index int
|
var index int
|
||||||
for _, wallet := range stack.AccountManager().Wallets() {
|
for _, wallet := range stack.AccountManager().Wallets() {
|
||||||
for _, account := range wallet.Accounts() {
|
for _, account := range wallet.Accounts() {
|
||||||
|
|
@ -220,7 +233,7 @@ func unlockAccount(ctx *cli.Context, ks *keystore.KeyStore, address string, i in
|
||||||
return accounts.Account{}, ""
|
return accounts.Account{}, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPassPhrase retrieves the passwor associated with an account, either fetched
|
// getPassPhrase retrieves the password associated with an account, either fetched
|
||||||
// from a list of preloaded passphrases, or requested interactively from the user.
|
// from a list of preloaded passphrases, or requested interactively from the user.
|
||||||
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
|
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
|
||||||
// If a list of passwords was supplied, retrieve from them
|
// If a list of passwords was supplied, retrieve from them
|
||||||
|
|
@ -278,7 +291,7 @@ func ambiguousAddrRecovery(ks *keystore.KeyStore, err *keystore.AmbiguousAddrErr
|
||||||
|
|
||||||
// accountCreate creates a new account into the keystore defined by the CLI flags.
|
// accountCreate creates a new account into the keystore defined by the CLI flags.
|
||||||
func accountCreate(ctx *cli.Context) error {
|
func accountCreate(ctx *cli.Context) error {
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
stack, _ := makeConfigNode(ctx)
|
||||||
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
@ -296,14 +309,16 @@ func accountUpdate(ctx *cli.Context) error {
|
||||||
if len(ctx.Args()) == 0 {
|
if len(ctx.Args()) == 0 {
|
||||||
utils.Fatalf("No accounts specified to update")
|
utils.Fatalf("No accounts specified to update")
|
||||||
}
|
}
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
stack, _ := makeConfigNode(ctx)
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
||||||
account, oldPassword := unlockAccount(ctx, ks, ctx.Args().First(), 0, nil)
|
for _, addr := range ctx.Args() {
|
||||||
|
account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil)
|
||||||
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
|
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
|
||||||
if err := ks.Update(account, oldPassword, newPassword); err != nil {
|
if err := ks.Update(account, oldPassword, newPassword); err != nil {
|
||||||
utils.Fatalf("Could not update the account: %v", err)
|
utils.Fatalf("Could not update the account: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,7 +332,7 @@ func importWallet(ctx *cli.Context) error {
|
||||||
utils.Fatalf("Could not read wallet file: %v", err)
|
utils.Fatalf("Could not read wallet file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
stack, _ := makeConfigNode(ctx)
|
||||||
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
|
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
@ -338,7 +353,7 @@ func accountImport(ctx *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to load the private key: %v", err)
|
utils.Fatalf("Failed to load the private key: %v", err)
|
||||||
}
|
}
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
stack, _ := makeConfigNode(ctx)
|
||||||
passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
|
||||||
|
|
@ -43,22 +43,22 @@ func tmpDatadirWithKeystore(t *testing.T) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountListEmpty(t *testing.T) {
|
func TestAccountListEmpty(t *testing.T) {
|
||||||
geth := runGeth(t, "account")
|
geth := runGeth(t, "account", "list")
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountList(t *testing.T) {
|
func TestAccountList(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t, "--datadir", datadir, "account")
|
geth := runGeth(t, "account", "list", "--datadir", datadir)
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}\keystore\UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
||||||
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}\keystore\aaa
|
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}\keystore\aaa
|
||||||
Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\keystore\zzz
|
Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\keystore\zzz
|
||||||
`)
|
`)
|
||||||
} else {
|
} else {
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
||||||
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa
|
Account #1: {f466859ead1932d743d622cb74fc058882e8648a} keystore://{{.Datadir}}/keystore/aaa
|
||||||
Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz
|
Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/keystore/zzz
|
||||||
|
|
@ -67,21 +67,21 @@ Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/k
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountNew(t *testing.T) {
|
func TestAccountNew(t *testing.T) {
|
||||||
geth := runGeth(t, "--lightkdf", "account", "new")
|
geth := runGeth(t, "account", "new", "--lightkdf")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
Your new account is locked with a password. Please give a password. Do not forget this password.
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
Repeat passphrase: {{.InputLine "foobar"}}
|
Repeat passphrase: {{.InputLine "foobar"}}
|
||||||
`)
|
`)
|
||||||
geth.expectRegexp(`Address: \{[0-9a-f]{40}\}\n`)
|
geth.ExpectRegexp(`Address: \{[0-9a-f]{40}\}\n`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccountNewBadRepeat(t *testing.T) {
|
func TestAccountNewBadRepeat(t *testing.T) {
|
||||||
geth := runGeth(t, "--lightkdf", "account", "new")
|
geth := runGeth(t, "account", "new", "--lightkdf")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
Your new account is locked with a password. Please give a password. Do not forget this password.
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "something"}}
|
Passphrase: {{.InputLine "something"}}
|
||||||
|
|
@ -92,11 +92,11 @@ Fatal: Passphrases do not match
|
||||||
|
|
||||||
func TestAccountUpdate(t *testing.T) {
|
func TestAccountUpdate(t *testing.T) {
|
||||||
datadir := tmpDatadirWithKeystore(t)
|
datadir := tmpDatadirWithKeystore(t)
|
||||||
geth := runGeth(t,
|
geth := runGeth(t, "account", "update",
|
||||||
"--datadir", datadir, "--lightkdf",
|
"--datadir", datadir, "--lightkdf",
|
||||||
"account", "update", "f466859ead1932d743d622cb74fc058882e8648a")
|
"f466859ead1932d743d622cb74fc058882e8648a")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
|
|
@ -107,9 +107,9 @@ Repeat passphrase: {{.InputLine "foobar2"}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalletImport(t *testing.T) {
|
func TestWalletImport(t *testing.T) {
|
||||||
geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json")
|
geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foo"}}
|
Passphrase: {{.InputLine "foo"}}
|
||||||
Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
|
Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
|
||||||
|
|
@ -122,9 +122,9 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalletImportBadPassword(t *testing.T) {
|
func TestWalletImportBadPassword(t *testing.T) {
|
||||||
geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json")
|
geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "wrong"}}
|
Passphrase: {{.InputLine "wrong"}}
|
||||||
Fatal: could not decrypt key with given passphrase
|
Fatal: could not decrypt key with given passphrase
|
||||||
|
|
@ -137,19 +137,19 @@ func TestUnlockFlag(t *testing.T) {
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
`)
|
`)
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
|
|
||||||
wantMessages := []string{
|
wantMessages := []string{
|
||||||
"Unlocked account",
|
"Unlocked account",
|
||||||
"=0xf466859ead1932d743d622cb74fc058882e8648a",
|
"=0xf466859ead1932d743d622cb74fc058882e8648a",
|
||||||
}
|
}
|
||||||
for _, m := range wantMessages {
|
for _, m := range wantMessages {
|
||||||
if !strings.Contains(geth.stderrText(), m) {
|
if !strings.Contains(geth.StderrText(), m) {
|
||||||
t.Errorf("stderr text does not contain %q", m)
|
t.Errorf("stderr text does not contain %q", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,8 +160,8 @@ func TestUnlockFlagWrongPassword(t *testing.T) {
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "wrong1"}}
|
Passphrase: {{.InputLine "wrong1"}}
|
||||||
|
|
@ -180,14 +180,14 @@ func TestUnlockFlagMultiIndex(t *testing.T) {
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--unlock", "0,2",
|
"--unlock", "0,2",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Unlocking account 0 | Attempt 1/3
|
Unlocking account 0 | Attempt 1/3
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
Unlocking account 2 | Attempt 1/3
|
Unlocking account 2 | Attempt 1/3
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
`)
|
`)
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
|
|
||||||
wantMessages := []string{
|
wantMessages := []string{
|
||||||
"Unlocked account",
|
"Unlocked account",
|
||||||
|
|
@ -195,7 +195,7 @@ Passphrase: {{.InputLine "foobar"}}
|
||||||
"=0x289d485d9771714cce91d3393d764e1311907acc",
|
"=0x289d485d9771714cce91d3393d764e1311907acc",
|
||||||
}
|
}
|
||||||
for _, m := range wantMessages {
|
for _, m := range wantMessages {
|
||||||
if !strings.Contains(geth.stderrText(), m) {
|
if !strings.Contains(geth.StderrText(), m) {
|
||||||
t.Errorf("stderr text does not contain %q", m)
|
t.Errorf("stderr text does not contain %q", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +207,7 @@ func TestUnlockFlagPasswordFile(t *testing.T) {
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--password", "testdata/passwords.txt", "--unlock", "0,2",
|
"--password", "testdata/passwords.txt", "--unlock", "0,2",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
|
|
||||||
wantMessages := []string{
|
wantMessages := []string{
|
||||||
"Unlocked account",
|
"Unlocked account",
|
||||||
|
|
@ -215,7 +215,7 @@ func TestUnlockFlagPasswordFile(t *testing.T) {
|
||||||
"=0x289d485d9771714cce91d3393d764e1311907acc",
|
"=0x289d485d9771714cce91d3393d764e1311907acc",
|
||||||
}
|
}
|
||||||
for _, m := range wantMessages {
|
for _, m := range wantMessages {
|
||||||
if !strings.Contains(geth.stderrText(), m) {
|
if !strings.Contains(geth.StderrText(), m) {
|
||||||
t.Errorf("stderr text does not contain %q", m)
|
t.Errorf("stderr text does not contain %q", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,8 +226,8 @@ func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) {
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
"--datadir", datadir, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
|
"--password", "testdata/wrong-passwords.txt", "--unlock", "0,2")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
|
Fatal: Failed to unlock account 0 (could not decrypt key with given passphrase)
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
@ -238,14 +238,14 @@ func TestUnlockFlagAmbiguous(t *testing.T) {
|
||||||
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
|
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a",
|
||||||
"js", "testdata/empty.js")
|
"js", "testdata/empty.js")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
|
|
||||||
// Helper for the expect template, returns absolute keystore path.
|
// Helper for the expect template, returns absolute keystore path.
|
||||||
geth.setTemplateFunc("keypath", func(file string) string {
|
geth.SetTemplateFunc("keypath", func(file string) string {
|
||||||
abs, _ := filepath.Abs(filepath.Join(store, file))
|
abs, _ := filepath.Abs(filepath.Join(store, file))
|
||||||
return abs
|
return abs
|
||||||
})
|
})
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "foobar"}}
|
Passphrase: {{.InputLine "foobar"}}
|
||||||
|
|
@ -257,14 +257,14 @@ Your passphrase unlocked keystore://{{keypath "1"}}
|
||||||
In order to avoid this warning, you need to remove the following duplicate key files:
|
In order to avoid this warning, you need to remove the following duplicate key files:
|
||||||
keystore://{{keypath "2"}}
|
keystore://{{keypath "2"}}
|
||||||
`)
|
`)
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
|
|
||||||
wantMessages := []string{
|
wantMessages := []string{
|
||||||
"Unlocked account",
|
"Unlocked account",
|
||||||
"=0xf466859ead1932d743d622cb74fc058882e8648a",
|
"=0xf466859ead1932d743d622cb74fc058882e8648a",
|
||||||
}
|
}
|
||||||
for _, m := range wantMessages {
|
for _, m := range wantMessages {
|
||||||
if !strings.Contains(geth.stderrText(), m) {
|
if !strings.Contains(geth.StderrText(), m) {
|
||||||
t.Errorf("stderr text does not contain %q", m)
|
t.Errorf("stderr text does not contain %q", m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -275,14 +275,14 @@ func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) {
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
|
"--keystore", store, "--nat", "none", "--nodiscover", "--dev",
|
||||||
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
"--unlock", "f466859ead1932d743d622cb74fc058882e8648a")
|
||||||
defer geth.expectExit()
|
defer geth.ExpectExit()
|
||||||
|
|
||||||
// Helper for the expect template, returns absolute keystore path.
|
// Helper for the expect template, returns absolute keystore path.
|
||||||
geth.setTemplateFunc("keypath", func(file string) string {
|
geth.SetTemplateFunc("keypath", func(file string) string {
|
||||||
abs, _ := filepath.Abs(filepath.Join(store, file))
|
abs, _ := filepath.Abs(filepath.Join(store, file))
|
||||||
return abs
|
return abs
|
||||||
})
|
})
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3
|
||||||
!! Unsupported terminal, password will be echoed.
|
!! Unsupported terminal, password will be echoed.
|
||||||
Passphrase: {{.InputLine "wrong"}}
|
Passphrase: {{.InputLine "wrong"}}
|
||||||
|
|
@ -292,5 +292,5 @@ Multiple key files exist for address f466859ead1932d743d622cb74fc058882e8648a:
|
||||||
Testing your passphrase against all of them...
|
Testing your passphrase against all of them...
|
||||||
Fatal: None of the listed files could be unlocked.
|
Fatal: None of the listed files could be unlocked.
|
||||||
`)
|
`)
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,12 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/cmd/internal/browser"
|
"github.com/ethereum/go-ethereum/cmd/internal/browser"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
cli "gopkg.in/urfave/cli.v1"
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bugCommand = cli.Command{
|
var bugCommand = cli.Command{
|
||||||
Action: reportBug,
|
Action: utils.MigrateFlags(reportBug),
|
||||||
Name: "bug",
|
Name: "bug",
|
||||||
Usage: "opens a window to report a bug on the geth repo",
|
Usage: "opens a window to report a bug on the geth repo",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
|
|
|
||||||
|
|
@ -40,80 +40,98 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
initCommand = cli.Command{
|
initCommand = cli.Command{
|
||||||
Action: initGenesis,
|
Action: utils.MigrateFlags(initGenesis),
|
||||||
Name: "init",
|
Name: "init",
|
||||||
Usage: "Bootstrap and initialize a new genesis block",
|
Usage: "Bootstrap and initialize a new genesis block",
|
||||||
ArgsUsage: "<genesisPath>",
|
ArgsUsage: "<genesisPath>",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
The init command initializes a new genesis block and definition for the network.
|
The init command initializes a new genesis block and definition for the network.
|
||||||
This is a destructive action and changes the network in which you will be
|
This is a destructive action and changes the network in which you will be
|
||||||
participating.
|
participating.
|
||||||
`,
|
|
||||||
|
It expects the genesis file as argument.`,
|
||||||
}
|
}
|
||||||
importCommand = cli.Command{
|
importCommand = cli.Command{
|
||||||
Action: importChain,
|
Action: utils.MigrateFlags(importChain),
|
||||||
Name: "import",
|
Name: "import",
|
||||||
Usage: "Import a blockchain file",
|
Usage: "Import a blockchain file",
|
||||||
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
|
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.CacheFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
The import command imports blocks from an RLP-encoded form. The form can be one file
|
The import command imports blocks from an RLP-encoded form. The form can be one file
|
||||||
with several RLP-encoded blocks, or several files can be used.
|
with several RLP-encoded blocks, or several files can be used.
|
||||||
|
|
||||||
If only one file is used, import error will result in failure. If several files are used,
|
If only one file is used, import error will result in failure. If several files are used,
|
||||||
processing will proceed even if an individual RLP-file import failure occurs.
|
processing will proceed even if an individual RLP-file import failure occurs.`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
exportCommand = cli.Command{
|
exportCommand = cli.Command{
|
||||||
Action: exportChain,
|
Action: utils.MigrateFlags(exportChain),
|
||||||
Name: "export",
|
Name: "export",
|
||||||
Usage: "Export blockchain into file",
|
Usage: "Export blockchain into file",
|
||||||
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
|
ArgsUsage: "<filename> [<blockNumFirst> <blockNumLast>]",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.CacheFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
Requires a first argument of the file to write to.
|
Requires a first argument of the file to write to.
|
||||||
Optional second and third arguments control the first and
|
Optional second and third arguments control the first and
|
||||||
last block to write. In this mode, the file will be appended
|
last block to write. In this mode, the file will be appended
|
||||||
if already existing.
|
if already existing.`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
removedbCommand = cli.Command{
|
removedbCommand = cli.Command{
|
||||||
Action: removeDB,
|
Action: utils.MigrateFlags(removeDB),
|
||||||
Name: "removedb",
|
Name: "removedb",
|
||||||
Usage: "Remove blockchain and state databases",
|
Usage: "Remove blockchain and state databases",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
TODO: Please write this
|
Remove blockchain and state databases`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
dumpCommand = cli.Command{
|
dumpCommand = cli.Command{
|
||||||
Action: dump,
|
Action: utils.MigrateFlags(dump),
|
||||||
Name: "dump",
|
Name: "dump",
|
||||||
Usage: "Dump a specific block from storage",
|
Usage: "Dump a specific block from storage",
|
||||||
ArgsUsage: "[<blockHash> | <blockNum>]...",
|
ArgsUsage: "[<blockHash> | <blockNum>]...",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.CacheFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
The arguments are interpreted as block numbers or hashes.
|
The arguments are interpreted as block numbers or hashes.
|
||||||
Use "ethereum dump 0" to dump the genesis block.
|
Use "ethereum dump 0" to dump the genesis block.`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// initGenesis will initialise the given JSON format genesis file and writes it as
|
// initGenesis will initialise the given JSON format genesis file and writes it as
|
||||||
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
|
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
|
||||||
func initGenesis(ctx *cli.Context) error {
|
func initGenesis(ctx *cli.Context) error {
|
||||||
|
// Make sure we have a valid genesis JSON
|
||||||
genesisPath := ctx.Args().First()
|
genesisPath := ctx.Args().First()
|
||||||
if len(genesisPath) == 0 {
|
if len(genesisPath) == 0 {
|
||||||
utils.Fatalf("must supply path to genesis JSON file")
|
utils.Fatalf("Must supply path to genesis JSON file")
|
||||||
}
|
}
|
||||||
|
|
||||||
stack := makeFullNode(ctx)
|
|
||||||
chaindb := utils.MakeChainDatabase(ctx, stack)
|
|
||||||
|
|
||||||
file, err := os.Open(genesisPath)
|
file, err := os.Open(genesisPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("failed to read genesis file: %v", err)
|
utils.Fatalf("Failed to read genesis file: %v", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
|
@ -121,12 +139,19 @@ func initGenesis(ctx *cli.Context) error {
|
||||||
if err := json.NewDecoder(file).Decode(genesis); err != nil {
|
if err := json.NewDecoder(file).Decode(genesis); err != nil {
|
||||||
utils.Fatalf("invalid genesis file: %v", err)
|
utils.Fatalf("invalid genesis file: %v", err)
|
||||||
}
|
}
|
||||||
|
// Open an initialise both full and light databases
|
||||||
|
stack := makeFullNode(ctx)
|
||||||
|
for _, name := range []string{"chaindata", "lightchaindata"} {
|
||||||
|
chaindb, err := stack.OpenDatabase(name, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to open database: %v", err)
|
||||||
|
}
|
||||||
_, hash, err := core.SetupGenesisBlock(chaindb, genesis)
|
_, hash, err := core.SetupGenesisBlock(chaindb, genesis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("failed to write genesis block: %v", err)
|
utils.Fatalf("Failed to write genesis block: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("Successfully wrote genesis state", "database", name, "hash", hash)
|
||||||
}
|
}
|
||||||
log.Info("Successfully wrote genesis state", "hash", hash)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,25 +269,30 @@ func exportChain(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeDB(ctx *cli.Context) error {
|
func removeDB(ctx *cli.Context) error {
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
stack, _ := makeConfigNode(ctx)
|
||||||
dbdir := stack.ResolvePath(utils.ChainDbName(ctx))
|
|
||||||
if !common.FileExist(dbdir) {
|
|
||||||
fmt.Println(dbdir, "does not exist")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for _, name := range []string{"chaindata", "lightchaindata"} {
|
||||||
|
// Ensure the database exists in the first place
|
||||||
|
logger := log.New("database", name)
|
||||||
|
|
||||||
|
dbdir := stack.ResolvePath(name)
|
||||||
|
if !common.FileExist(dbdir) {
|
||||||
|
logger.Info("Database doesn't exist, skipping", "path", dbdir)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Confirm removal and execute
|
||||||
fmt.Println(dbdir)
|
fmt.Println(dbdir)
|
||||||
confirm, err := console.Stdin.PromptConfirm("Remove this database?")
|
confirm, err := console.Stdin.PromptConfirm("Remove this database?")
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
case !confirm:
|
case !confirm:
|
||||||
fmt.Println("Operation aborted")
|
logger.Warn("Database deletion aborted")
|
||||||
default:
|
default:
|
||||||
fmt.Println("Removing...")
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
os.RemoveAll(dbdir)
|
os.RemoveAll(dbdir)
|
||||||
fmt.Printf("Removed in %v\n", time.Since(start))
|
logger.Info("Database successfully deleted", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +312,7 @@ func dump(ctx *cli.Context) error {
|
||||||
fmt.Println("{}")
|
fmt.Println("{}")
|
||||||
utils.Fatalf("block not found")
|
utils.Fatalf("block not found")
|
||||||
} else {
|
} else {
|
||||||
state, err := state.New(block.Root(), chainDb)
|
state, err := state.New(block.Root(), state.NewDatabase(chainDb))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("could not create new state: %v", err)
|
utils.Fatalf("could not create new state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
208
cmd/geth/config.go
Normal file
208
cmd/geth/config.go
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/contracts/release"
|
||||||
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||||
|
"github.com/naoina/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dumpConfigCommand = cli.Command{
|
||||||
|
Action: utils.MigrateFlags(dumpConfig),
|
||||||
|
Name: "dumpconfig",
|
||||||
|
Usage: "Show configuration values",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Flags: append(append(nodeFlags, rpcFlags...), whisperFlags...),
|
||||||
|
Category: "MISCELLANEOUS COMMANDS",
|
||||||
|
Description: `The dumpconfig command shows configuration values.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
configFileFlag = cli.StringFlag{
|
||||||
|
Name: "config",
|
||||||
|
Usage: "TOML configuration file",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// These settings ensure that TOML keys use the same names as Go struct fields.
|
||||||
|
var tomlSettings = toml.Config{
|
||||||
|
NormFieldName: func(rt reflect.Type, key string) string {
|
||||||
|
return key
|
||||||
|
},
|
||||||
|
FieldToKey: func(rt reflect.Type, field string) string {
|
||||||
|
return field
|
||||||
|
},
|
||||||
|
MissingField: func(rt reflect.Type, field string) error {
|
||||||
|
link := ""
|
||||||
|
if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" {
|
||||||
|
link = fmt.Sprintf(", see https://godoc.org/%s#%s for available fields", rt.PkgPath(), rt.Name())
|
||||||
|
}
|
||||||
|
return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type ethstatsConfig struct {
|
||||||
|
URL string `toml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type gethConfig struct {
|
||||||
|
Eth eth.Config
|
||||||
|
Shh whisper.Config
|
||||||
|
Node node.Config
|
||||||
|
Ethstats ethstatsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(file string, cfg *gethConfig) error {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
err = tomlSettings.NewDecoder(bufio.NewReader(f)).Decode(cfg)
|
||||||
|
// Add file name to errors that have a line number.
|
||||||
|
if _, ok := err.(*toml.LineError); ok {
|
||||||
|
err = errors.New(file + ", " + err.Error())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultNodeConfig() node.Config {
|
||||||
|
cfg := node.DefaultConfig
|
||||||
|
cfg.Name = clientIdentifier
|
||||||
|
cfg.Version = params.VersionWithCommit(gitCommit)
|
||||||
|
cfg.HTTPModules = append(cfg.HTTPModules, "eth", "shh")
|
||||||
|
cfg.WSModules = append(cfg.WSModules, "eth", "shh")
|
||||||
|
cfg.IPCPath = "geth.ipc"
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
|
||||||
|
// Load defaults.
|
||||||
|
cfg := gethConfig{
|
||||||
|
Eth: eth.DefaultConfig,
|
||||||
|
Shh: whisper.DefaultConfig,
|
||||||
|
Node: defaultNodeConfig(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load config file.
|
||||||
|
if file := ctx.GlobalString(configFileFlag.Name); file != "" {
|
||||||
|
if err := loadConfig(file, &cfg); err != nil {
|
||||||
|
utils.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply flags.
|
||||||
|
utils.SetNodeConfig(ctx, &cfg.Node)
|
||||||
|
stack, err := node.New(&cfg.Node)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to create the protocol stack: %v", err)
|
||||||
|
}
|
||||||
|
utils.SetEthConfig(ctx, stack, &cfg.Eth)
|
||||||
|
if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) {
|
||||||
|
cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SetShhConfig(ctx, stack, &cfg.Shh)
|
||||||
|
|
||||||
|
return stack, cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// enableWhisper returns true in case one of the whisper flags is set.
|
||||||
|
func enableWhisper(ctx *cli.Context) bool {
|
||||||
|
for _, flag := range whisperFlags {
|
||||||
|
if ctx.GlobalIsSet(flag.GetName()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
|
stack, cfg := makeConfigNode(ctx)
|
||||||
|
|
||||||
|
utils.RegisterEthService(stack, &cfg.Eth)
|
||||||
|
|
||||||
|
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
||||||
|
shhEnabled := enableWhisper(ctx)
|
||||||
|
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
||||||
|
if shhEnabled || shhAutoEnabled {
|
||||||
|
if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
|
||||||
|
cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
|
||||||
|
}
|
||||||
|
if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
|
||||||
|
cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
|
||||||
|
}
|
||||||
|
utils.RegisterShhService(stack, &cfg.Shh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the Ethereum Stats daemon if requested.
|
||||||
|
if cfg.Ethstats.URL != "" {
|
||||||
|
utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the release oracle service so it boots along with node.
|
||||||
|
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
|
config := release.Config{
|
||||||
|
Oracle: relOracle,
|
||||||
|
Major: uint32(params.VersionMajor),
|
||||||
|
Minor: uint32(params.VersionMinor),
|
||||||
|
Patch: uint32(params.VersionPatch),
|
||||||
|
}
|
||||||
|
commit, _ := hex.DecodeString(gitCommit)
|
||||||
|
copy(config.Commit[:], commit)
|
||||||
|
return release.NewReleaseService(ctx, config)
|
||||||
|
}); err != nil {
|
||||||
|
utils.Fatalf("Failed to register the Geth release oracle service: %v", err)
|
||||||
|
}
|
||||||
|
return stack
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpConfig is the dumpconfig command.
|
||||||
|
func dumpConfig(ctx *cli.Context) error {
|
||||||
|
_, cfg := makeConfigNode(ctx)
|
||||||
|
comment := ""
|
||||||
|
|
||||||
|
if cfg.Eth.Genesis != nil {
|
||||||
|
cfg.Eth.Genesis = nil
|
||||||
|
comment += "# Note: this config doesn't contain the genesis block.\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := tomlSettings.Marshal(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
io.WriteString(os.Stdout, comment)
|
||||||
|
os.Stdout.Write(out)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -29,41 +29,44 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
consoleFlags = []cli.Flag{utils.JSpathFlag, utils.ExecFlag, utils.PreloadJSFlag}
|
||||||
|
|
||||||
consoleCommand = cli.Command{
|
consoleCommand = cli.Command{
|
||||||
Action: localConsole,
|
Action: utils.MigrateFlags(localConsole),
|
||||||
Name: "console",
|
Name: "console",
|
||||||
Usage: "Start an interactive JavaScript environment",
|
Usage: "Start an interactive JavaScript environment",
|
||||||
ArgsUsage: "", // TODO: Write this!
|
Flags: append(append(append(nodeFlags, rpcFlags...), consoleFlags...), whisperFlags...),
|
||||||
Category: "CONSOLE COMMANDS",
|
Category: "CONSOLE COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
The Geth console is an interactive shell for the JavaScript runtime environment
|
||||||
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attachCommand = cli.Command{
|
attachCommand = cli.Command{
|
||||||
Action: remoteConsole,
|
Action: utils.MigrateFlags(remoteConsole),
|
||||||
Name: "attach",
|
Name: "attach",
|
||||||
Usage: "Start an interactive JavaScript environment (connect to node)",
|
Usage: "Start an interactive JavaScript environment (connect to node)",
|
||||||
ArgsUsage: "", // TODO: Write this!
|
ArgsUsage: "[endpoint]",
|
||||||
|
Flags: append(consoleFlags, utils.DataDirFlag),
|
||||||
Category: "CONSOLE COMMANDS",
|
Category: "CONSOLE COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
The Geth console is an interactive shell for the JavaScript runtime environment
|
||||||
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
|
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
|
||||||
This command allows to open a console on a running geth node.
|
This command allows to open a console on a running geth node.`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
javascriptCommand = cli.Command{
|
javascriptCommand = cli.Command{
|
||||||
Action: ephemeralConsole,
|
Action: utils.MigrateFlags(ephemeralConsole),
|
||||||
Name: "js",
|
Name: "js",
|
||||||
Usage: "Execute the specified JavaScript files",
|
Usage: "Execute the specified JavaScript files",
|
||||||
ArgsUsage: "", // TODO: Write this!
|
ArgsUsage: "<jsfile> [jsfile...]",
|
||||||
|
Flags: append(nodeFlags, consoleFlags...),
|
||||||
Category: "CONSOLE COMMANDS",
|
Category: "CONSOLE COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
The JavaScript VM exposes a node admin interface as well as the Ðapp
|
The JavaScript VM exposes a node admin interface as well as the Ðapp
|
||||||
JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console`,
|
||||||
`,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -81,11 +84,12 @@ func localConsole(ctx *cli.Context) error {
|
||||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||||
}
|
}
|
||||||
config := console.Config{
|
config := console.Config{
|
||||||
DataDir: node.DataDir(),
|
DataDir: utils.MakeDataDir(ctx),
|
||||||
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
||||||
Client: client,
|
Client: client,
|
||||||
Preload: utils.MakeConsolePreloads(ctx),
|
Preload: utils.MakeConsolePreloads(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
console, err := console.New(config)
|
console, err := console.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
||||||
|
|
@ -118,17 +122,18 @@ func remoteConsole(ctx *cli.Context) error {
|
||||||
Client: client,
|
Client: client,
|
||||||
Preload: utils.MakeConsolePreloads(ctx),
|
Preload: utils.MakeConsolePreloads(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
console, err := console.New(config)
|
console, err := console.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
||||||
}
|
}
|
||||||
defer console.Stop(false)
|
defer console.Stop(false)
|
||||||
|
|
||||||
// If only a short execution was requested, evaluate and return
|
|
||||||
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
|
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
|
||||||
console.Evaluate(script)
|
console.Evaluate(script)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise print the welcome screen and enter interactive mode
|
// Otherwise print the welcome screen and enter interactive mode
|
||||||
console.Welcome()
|
console.Welcome()
|
||||||
console.Interactive()
|
console.Interactive()
|
||||||
|
|
@ -151,7 +156,7 @@ func dialRPC(endpoint string) (*rpc.Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
||||||
// console to it, and each of the files specified as arguments and tears the
|
// console to it, executes each of the files specified as arguments and tears
|
||||||
// everything down.
|
// everything down.
|
||||||
func ephemeralConsole(ctx *cli.Context) error {
|
func ephemeralConsole(ctx *cli.Context) error {
|
||||||
// Create and start the node based on the CLI flags
|
// Create and start the node based on the CLI flags
|
||||||
|
|
@ -165,11 +170,12 @@ func ephemeralConsole(ctx *cli.Context) error {
|
||||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||||
}
|
}
|
||||||
config := console.Config{
|
config := console.Config{
|
||||||
DataDir: node.DataDir(),
|
DataDir: utils.MakeDataDir(ctx),
|
||||||
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
||||||
Client: client,
|
Client: client,
|
||||||
Preload: utils.MakeConsolePreloads(ctx),
|
Preload: utils.MakeConsolePreloads(ctx),
|
||||||
}
|
}
|
||||||
|
|
||||||
console, err := console.New(config)
|
console, err := console.New(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,17 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipcAPIs = "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0"
|
||||||
|
httpAPIs = "eth:1.0 net:1.0 rpc:1.0 web3:1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tests that a node embedded within a console can be started up properly and
|
// Tests that a node embedded within a console can be started up properly and
|
||||||
|
|
@ -44,30 +47,26 @@ func TestConsoleWelcome(t *testing.T) {
|
||||||
"console")
|
"console")
|
||||||
|
|
||||||
// Gather all the infos the welcome message needs to contain
|
// Gather all the infos the welcome message needs to contain
|
||||||
geth.setTemplateFunc("goos", func() string { return runtime.GOOS })
|
geth.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||||
geth.setTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
geth.setTemplateFunc("gover", runtime.Version)
|
geth.SetTemplateFunc("gover", runtime.Version)
|
||||||
geth.setTemplateFunc("gethver", func() string { return params.Version })
|
geth.SetTemplateFunc("gethver", func() string { return params.Version })
|
||||||
geth.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||||
geth.setTemplateFunc("apis", func() []string {
|
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||||
apis := append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi)
|
|
||||||
sort.Strings(apis)
|
|
||||||
return apis
|
|
||||||
})
|
|
||||||
|
|
||||||
// Verify the actual welcome message to the required template
|
// Verify the actual welcome message to the required template
|
||||||
geth.expect(`
|
geth.Expect(`
|
||||||
Welcome to the Geth JavaScript console!
|
Welcome to the Geth JavaScript console!
|
||||||
|
|
||||||
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
||||||
coinbase: {{.Etherbase}}
|
coinbase: {{.Etherbase}}
|
||||||
at block: 0 ({{niltime}})
|
at block: 0 ({{niltime}})
|
||||||
datadir: {{.Datadir}}
|
datadir: {{.Datadir}}
|
||||||
modules:{{range apis}} {{.}}:1.0{{end}}
|
modules: {{apis}}
|
||||||
|
|
||||||
> {{.InputLine "exit"}}
|
> {{.InputLine "exit"}}
|
||||||
`)
|
`)
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that a console can be attached to a running node via various means.
|
// Tests that a console can be attached to a running node via various means.
|
||||||
|
|
@ -89,10 +88,10 @@ func TestIPCAttachWelcome(t *testing.T) {
|
||||||
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
|
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
||||||
testAttachWelcome(t, geth, "ipc:"+ipc)
|
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
|
||||||
|
|
||||||
geth.interrupt()
|
geth.Interrupt()
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPAttachWelcome(t *testing.T) {
|
func TestHTTPAttachWelcome(t *testing.T) {
|
||||||
|
|
@ -103,10 +102,10 @@ func TestHTTPAttachWelcome(t *testing.T) {
|
||||||
"--etherbase", coinbase, "--rpc", "--rpcport", port)
|
"--etherbase", coinbase, "--rpc", "--rpcport", port)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
||||||
testAttachWelcome(t, geth, "http://localhost:"+port)
|
testAttachWelcome(t, geth, "http://localhost:"+port, httpAPIs)
|
||||||
|
|
||||||
geth.interrupt()
|
geth.Interrupt()
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWSAttachWelcome(t *testing.T) {
|
func TestWSAttachWelcome(t *testing.T) {
|
||||||
|
|
@ -118,51 +117,42 @@ func TestWSAttachWelcome(t *testing.T) {
|
||||||
"--etherbase", coinbase, "--ws", "--wsport", port)
|
"--etherbase", coinbase, "--ws", "--wsport", port)
|
||||||
|
|
||||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
||||||
testAttachWelcome(t, geth, "ws://localhost:"+port)
|
testAttachWelcome(t, geth, "ws://localhost:"+port, httpAPIs)
|
||||||
|
|
||||||
geth.interrupt()
|
geth.Interrupt()
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint string) {
|
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
// Attach to a running geth note and terminate immediately
|
// Attach to a running geth note and terminate immediately
|
||||||
attach := runGeth(t, "attach", endpoint)
|
attach := runGeth(t, "attach", endpoint)
|
||||||
defer attach.expectExit()
|
defer attach.ExpectExit()
|
||||||
attach.stdin.Close()
|
attach.CloseStdin()
|
||||||
|
|
||||||
// Gather all the infos the welcome message needs to contain
|
// Gather all the infos the welcome message needs to contain
|
||||||
attach.setTemplateFunc("goos", func() string { return runtime.GOOS })
|
attach.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||||
attach.setTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
attach.setTemplateFunc("gover", runtime.Version)
|
attach.SetTemplateFunc("gover", runtime.Version)
|
||||||
attach.setTemplateFunc("gethver", func() string { return params.Version })
|
attach.SetTemplateFunc("gethver", func() string { return params.Version })
|
||||||
attach.setTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
||||||
attach.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||||
attach.setTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||||
attach.setTemplateFunc("datadir", func() string { return geth.Datadir })
|
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
|
||||||
attach.setTemplateFunc("apis", func() []string {
|
attach.SetTemplateFunc("apis", func() string { return apis })
|
||||||
var apis []string
|
|
||||||
if strings.HasPrefix(endpoint, "ipc") {
|
|
||||||
apis = append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi)
|
|
||||||
} else {
|
|
||||||
apis = append(strings.Split(rpc.DefaultHTTPApis, ","), rpc.MetadataApi)
|
|
||||||
}
|
|
||||||
sort.Strings(apis)
|
|
||||||
return apis
|
|
||||||
})
|
|
||||||
|
|
||||||
// Verify the actual welcome message to the required template
|
// Verify the actual welcome message to the required template
|
||||||
attach.expect(`
|
attach.Expect(`
|
||||||
Welcome to the Geth JavaScript console!
|
Welcome to the Geth JavaScript console!
|
||||||
|
|
||||||
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
|
||||||
coinbase: {{etherbase}}
|
coinbase: {{etherbase}}
|
||||||
at block: 0 ({{niltime}}){{if ipc}}
|
at block: 0 ({{niltime}}){{if ipc}}
|
||||||
datadir: {{datadir}}{{end}}
|
datadir: {{datadir}}{{end}}
|
||||||
modules:{{range apis}} {{.}}:1.0{{end}}
|
modules: {{apis}}
|
||||||
|
|
||||||
> {{.InputLine "exit" }}
|
> {{.InputLine "exit" }}
|
||||||
`)
|
`)
|
||||||
attach.expectExit()
|
attach.ExpectExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
// trulyRandInt generates a crypto random integer used by the console tests to
|
// trulyRandInt generates a crypto random integer used by the console tests to
|
||||||
|
|
|
||||||
|
|
@ -84,27 +84,24 @@ var daoGenesisForkBlock = big.NewInt(314)
|
||||||
// set in the database after various initialization procedures and invocations.
|
// set in the database after various initialization procedures and invocations.
|
||||||
func TestDAOForkBlockNewChain(t *testing.T) {
|
func TestDAOForkBlockNewChain(t *testing.T) {
|
||||||
for i, arg := range []struct {
|
for i, arg := range []struct {
|
||||||
testnet bool
|
|
||||||
genesis string
|
genesis string
|
||||||
expectBlock *big.Int
|
expectBlock *big.Int
|
||||||
expectVote bool
|
expectVote bool
|
||||||
}{
|
}{
|
||||||
// Test DAO Default Mainnet
|
// Test DAO Default Mainnet
|
||||||
{false, "", params.MainNetDAOForkBlock, true},
|
{"", params.MainnetChainConfig.DAOForkBlock, true},
|
||||||
// test DAO Default Testnet
|
|
||||||
{true, "", params.TestNetDAOForkBlock, true},
|
|
||||||
// test DAO Init Old Privnet
|
// test DAO Init Old Privnet
|
||||||
{false, daoOldGenesis, nil, false},
|
{daoOldGenesis, nil, false},
|
||||||
// test DAO Default No Fork Privnet
|
// test DAO Default No Fork Privnet
|
||||||
{false, daoNoForkGenesis, daoGenesisForkBlock, false},
|
{daoNoForkGenesis, daoGenesisForkBlock, false},
|
||||||
// test DAO Default Pro Fork Privnet
|
// test DAO Default Pro Fork Privnet
|
||||||
{false, daoProForkGenesis, daoGenesisForkBlock, true},
|
{daoProForkGenesis, daoGenesisForkBlock, true},
|
||||||
} {
|
} {
|
||||||
testDAOForkBlockNewChain(t, i, arg.testnet, arg.genesis, arg.expectBlock, arg.expectVote)
|
testDAOForkBlockNewChain(t, i, arg.genesis, arg.expectBlock, arg.expectVote)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis string, expectBlock *big.Int, expectVote bool) {
|
func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBlock *big.Int, expectVote bool) {
|
||||||
// Create a temporary data directory to use and inspect later
|
// Create a temporary data directory to use and inspect later
|
||||||
datadir := tmpdir(t)
|
datadir := tmpdir(t)
|
||||||
defer os.RemoveAll(datadir)
|
defer os.RemoveAll(datadir)
|
||||||
|
|
@ -115,21 +112,15 @@ func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis stri
|
||||||
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
|
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
|
||||||
t.Fatalf("test %d: failed to write genesis file: %v", test, err)
|
t.Fatalf("test %d: failed to write genesis file: %v", test, err)
|
||||||
}
|
}
|
||||||
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
|
runGeth(t, "--datadir", datadir, "init", json).WaitExit()
|
||||||
} else {
|
} else {
|
||||||
// Force chain initialization
|
// Force chain initialization
|
||||||
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
|
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
|
||||||
if testnet {
|
|
||||||
args = append(args, "--testnet")
|
|
||||||
}
|
|
||||||
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
|
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
|
||||||
geth.cmd.Wait()
|
geth.WaitExit()
|
||||||
}
|
}
|
||||||
// Retrieve the DAO config flag from the database
|
// Retrieve the DAO config flag from the database
|
||||||
path := filepath.Join(datadir, "geth", "chaindata")
|
path := filepath.Join(datadir, "geth", "chaindata")
|
||||||
if testnet && genesis == "" {
|
|
||||||
path = filepath.Join(datadir, "testnet", "geth", "chaindata")
|
|
||||||
}
|
|
||||||
db, err := ethdb.NewLDBDatabase(path, 0, 0)
|
db, err := ethdb.NewLDBDatabase(path, 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("test %d: failed to open test database: %v", test, err)
|
t.Fatalf("test %d: failed to open test database: %v", test, err)
|
||||||
|
|
@ -137,9 +128,6 @@ func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis stri
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||||
if testnet {
|
|
||||||
genesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
|
||||||
}
|
|
||||||
if genesis != "" {
|
if genesis != "" {
|
||||||
genesisHash = daoGenesisHash
|
genesisHash = daoGenesisHash
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -97,14 +97,14 @@ func TestCustomGenesis(t *testing.T) {
|
||||||
if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil {
|
if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil {
|
||||||
t.Fatalf("test %d: failed to write genesis file: %v", i, err)
|
t.Fatalf("test %d: failed to write genesis file: %v", i, err)
|
||||||
}
|
}
|
||||||
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
|
runGeth(t, "--datadir", datadir, "init", json).WaitExit()
|
||||||
|
|
||||||
// Query the custom genesis block
|
// Query the custom genesis block
|
||||||
geth := runGeth(t,
|
geth := runGeth(t,
|
||||||
"--datadir", datadir, "--maxpeers", "0", "--port", "0",
|
"--datadir", datadir, "--maxpeers", "0", "--port", "0",
|
||||||
"--nodiscover", "--nat", "none", "--ipcdisable",
|
"--nodiscover", "--nat", "none", "--ipcdisable",
|
||||||
"--exec", tt.query, "console")
|
"--exec", tt.query, "console")
|
||||||
geth.expectRegexp(tt.result)
|
geth.ExpectRegexp(tt.result)
|
||||||
geth.expectExit()
|
geth.ExpectExit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
214
cmd/geth/main.go
214
cmd/geth/main.go
|
|
@ -18,7 +18,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
@ -29,17 +28,13 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/console"
|
"github.com/ethereum/go-ethereum/console"
|
||||||
"github.com/ethereum/go-ethereum/contracts/release"
|
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -54,13 +49,95 @@ var (
|
||||||
relOracle = common.HexToAddress("0xfa7b9770ca4cb04296cac84f37736d4041251cdf")
|
relOracle = common.HexToAddress("0xfa7b9770ca4cb04296cac84f37736d4041251cdf")
|
||||||
// The app that holds all commands and flags.
|
// The app that holds all commands and flags.
|
||||||
app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
|
app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
|
||||||
|
// flags that configure the node
|
||||||
|
nodeFlags = []cli.Flag{
|
||||||
|
utils.IdentityFlag,
|
||||||
|
utils.UnlockedAccountFlag,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
utils.BootnodesFlag,
|
||||||
|
utils.BootnodesV4Flag,
|
||||||
|
utils.BootnodesV5Flag,
|
||||||
|
utils.DataDirFlag,
|
||||||
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.NoUSBFlag,
|
||||||
|
utils.EthashCacheDirFlag,
|
||||||
|
utils.EthashCachesInMemoryFlag,
|
||||||
|
utils.EthashCachesOnDiskFlag,
|
||||||
|
utils.EthashDatasetDirFlag,
|
||||||
|
utils.EthashDatasetsInMemoryFlag,
|
||||||
|
utils.EthashDatasetsOnDiskFlag,
|
||||||
|
utils.TxPoolNoLocalsFlag,
|
||||||
|
utils.TxPoolPriceLimitFlag,
|
||||||
|
utils.TxPoolPriceBumpFlag,
|
||||||
|
utils.TxPoolAccountSlotsFlag,
|
||||||
|
utils.TxPoolGlobalSlotsFlag,
|
||||||
|
utils.TxPoolAccountQueueFlag,
|
||||||
|
utils.TxPoolGlobalQueueFlag,
|
||||||
|
utils.TxPoolLifetimeFlag,
|
||||||
|
utils.FastSyncFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
|
utils.SyncModeFlag,
|
||||||
|
utils.LightServFlag,
|
||||||
|
utils.LightPeersFlag,
|
||||||
|
utils.LightKDFFlag,
|
||||||
|
utils.CacheFlag,
|
||||||
|
utils.TrieCacheGenFlag,
|
||||||
|
utils.ListenPortFlag,
|
||||||
|
utils.MaxPeersFlag,
|
||||||
|
utils.MaxPendingPeersFlag,
|
||||||
|
utils.EtherbaseFlag,
|
||||||
|
utils.GasPriceFlag,
|
||||||
|
utils.MinerThreadsFlag,
|
||||||
|
utils.MiningEnabledFlag,
|
||||||
|
utils.TargetGasLimitFlag,
|
||||||
|
utils.NATFlag,
|
||||||
|
utils.NoDiscoverFlag,
|
||||||
|
utils.DiscoveryV5Flag,
|
||||||
|
utils.NetrestrictFlag,
|
||||||
|
utils.NodeKeyFileFlag,
|
||||||
|
utils.NodeKeyHexFlag,
|
||||||
|
utils.DevModeFlag,
|
||||||
|
utils.TestnetFlag,
|
||||||
|
utils.RinkebyFlag,
|
||||||
|
utils.VMEnableDebugFlag,
|
||||||
|
utils.NetworkIdFlag,
|
||||||
|
utils.RPCCORSDomainFlag,
|
||||||
|
utils.EthStatsURLFlag,
|
||||||
|
utils.MetricsEnabledFlag,
|
||||||
|
utils.FakePoWFlag,
|
||||||
|
utils.NoCompactionFlag,
|
||||||
|
utils.GpoBlocksFlag,
|
||||||
|
utils.GpoPercentileFlag,
|
||||||
|
utils.ExtraDataFlag,
|
||||||
|
configFileFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcFlags = []cli.Flag{
|
||||||
|
utils.RPCEnabledFlag,
|
||||||
|
utils.RPCListenAddrFlag,
|
||||||
|
utils.RPCPortFlag,
|
||||||
|
utils.RPCApiFlag,
|
||||||
|
utils.WSEnabledFlag,
|
||||||
|
utils.WSListenAddrFlag,
|
||||||
|
utils.WSPortFlag,
|
||||||
|
utils.WSApiFlag,
|
||||||
|
utils.WSAllowedOriginsFlag,
|
||||||
|
utils.IPCDisabledFlag,
|
||||||
|
utils.IPCPathFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
whisperFlags = []cli.Flag{
|
||||||
|
utils.WhisperEnabledFlag,
|
||||||
|
utils.WhisperMaxMessageSizeFlag,
|
||||||
|
utils.WhisperMinPOWFlag,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Initialize the CLI app and start Geth
|
// Initialize the CLI app and start Geth
|
||||||
app.Action = geth
|
app.Action = geth
|
||||||
app.HideVersion = true // we have a command to print the version
|
app.HideVersion = true // we have a command to print the version
|
||||||
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
app.Copyright = "Copyright 2013-2017 The go-ethereum Authors"
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
// See chaincmd.go:
|
// See chaincmd.go:
|
||||||
initCommand,
|
initCommand,
|
||||||
|
|
@ -82,76 +159,15 @@ func init() {
|
||||||
versionCommand,
|
versionCommand,
|
||||||
bugCommand,
|
bugCommand,
|
||||||
licenseCommand,
|
licenseCommand,
|
||||||
|
// See config.go
|
||||||
|
dumpConfigCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = append(app.Flags, nodeFlags...)
|
||||||
utils.IdentityFlag,
|
app.Flags = append(app.Flags, rpcFlags...)
|
||||||
utils.UnlockedAccountFlag,
|
app.Flags = append(app.Flags, consoleFlags...)
|
||||||
utils.PasswordFileFlag,
|
|
||||||
utils.BootnodesFlag,
|
|
||||||
utils.DataDirFlag,
|
|
||||||
utils.KeyStoreDirFlag,
|
|
||||||
utils.EthashCacheDirFlag,
|
|
||||||
utils.EthashCachesInMemoryFlag,
|
|
||||||
utils.EthashCachesOnDiskFlag,
|
|
||||||
utils.EthashDatasetDirFlag,
|
|
||||||
utils.EthashDatasetsInMemoryFlag,
|
|
||||||
utils.EthashDatasetsOnDiskFlag,
|
|
||||||
utils.FastSyncFlag,
|
|
||||||
utils.LightModeFlag,
|
|
||||||
utils.LightServFlag,
|
|
||||||
utils.LightPeersFlag,
|
|
||||||
utils.LightKDFFlag,
|
|
||||||
utils.CacheFlag,
|
|
||||||
utils.TrieCacheGenFlag,
|
|
||||||
utils.JSpathFlag,
|
|
||||||
utils.ListenPortFlag,
|
|
||||||
utils.MaxPeersFlag,
|
|
||||||
utils.MaxPendingPeersFlag,
|
|
||||||
utils.EtherbaseFlag,
|
|
||||||
utils.GasPriceFlag,
|
|
||||||
utils.MinerThreadsFlag,
|
|
||||||
utils.MiningEnabledFlag,
|
|
||||||
utils.TargetGasLimitFlag,
|
|
||||||
utils.NATFlag,
|
|
||||||
utils.NoDiscoverFlag,
|
|
||||||
utils.DiscoveryV5Flag,
|
|
||||||
utils.NetrestrictFlag,
|
|
||||||
utils.NodeKeyFileFlag,
|
|
||||||
utils.NodeKeyHexFlag,
|
|
||||||
utils.RPCEnabledFlag,
|
|
||||||
utils.RPCListenAddrFlag,
|
|
||||||
utils.RPCPortFlag,
|
|
||||||
utils.RPCApiFlag,
|
|
||||||
utils.WSEnabledFlag,
|
|
||||||
utils.WSListenAddrFlag,
|
|
||||||
utils.WSPortFlag,
|
|
||||||
utils.WSApiFlag,
|
|
||||||
utils.WSAllowedOriginsFlag,
|
|
||||||
utils.IPCDisabledFlag,
|
|
||||||
utils.IPCApiFlag,
|
|
||||||
utils.IPCPathFlag,
|
|
||||||
utils.ExecFlag,
|
|
||||||
utils.PreloadJSFlag,
|
|
||||||
utils.WhisperEnabledFlag,
|
|
||||||
utils.DevModeFlag,
|
|
||||||
utils.TestNetFlag,
|
|
||||||
utils.VMForceJitFlag,
|
|
||||||
utils.VMJitCacheFlag,
|
|
||||||
utils.VMEnableJitFlag,
|
|
||||||
utils.VMEnableDebugFlag,
|
|
||||||
utils.NetworkIdFlag,
|
|
||||||
utils.RPCCORSDomainFlag,
|
|
||||||
utils.EthStatsURLFlag,
|
|
||||||
utils.MetricsEnabledFlag,
|
|
||||||
utils.FakePoWFlag,
|
|
||||||
utils.NoCompactionFlag,
|
|
||||||
utils.SolcPathFlag,
|
|
||||||
utils.GpoBlocksFlag,
|
|
||||||
utils.GpoPercentileFlag,
|
|
||||||
utils.ExtraDataFlag,
|
|
||||||
}
|
|
||||||
app.Flags = append(app.Flags, debug.Flags...)
|
app.Flags = append(app.Flags, debug.Flags...)
|
||||||
|
app.Flags = append(app.Flags, whisperFlags...)
|
||||||
|
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
@ -189,52 +205,6 @@ func geth(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFullNode(ctx *cli.Context) *node.Node {
|
|
||||||
// Create the default extradata and construct the base node
|
|
||||||
var clientInfo = struct {
|
|
||||||
Version uint
|
|
||||||
Name string
|
|
||||||
GoVersion string
|
|
||||||
Os string
|
|
||||||
}{uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch), clientIdentifier, runtime.Version(), runtime.GOOS}
|
|
||||||
extra, err := rlp.EncodeToBytes(clientInfo)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Failed to set canonical miner information", "err", err)
|
|
||||||
}
|
|
||||||
if uint64(len(extra)) > params.MaximumExtraDataSize {
|
|
||||||
log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
|
|
||||||
extra = nil
|
|
||||||
}
|
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
|
||||||
utils.RegisterEthService(ctx, stack, extra)
|
|
||||||
|
|
||||||
// Whisper must be explicitly enabled, but is auto-enabled in --dev mode.
|
|
||||||
shhEnabled := ctx.GlobalBool(utils.WhisperEnabledFlag.Name)
|
|
||||||
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
|
|
||||||
if shhEnabled || shhAutoEnabled {
|
|
||||||
utils.RegisterShhService(stack)
|
|
||||||
}
|
|
||||||
// Add the Ethereum Stats daemon if requested
|
|
||||||
if url := ctx.GlobalString(utils.EthStatsURLFlag.Name); url != "" {
|
|
||||||
utils.RegisterEthStatsService(stack, url)
|
|
||||||
}
|
|
||||||
// Add the release oracle service so it boots along with node.
|
|
||||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
|
||||||
config := release.Config{
|
|
||||||
Oracle: relOracle,
|
|
||||||
Major: uint32(params.VersionMajor),
|
|
||||||
Minor: uint32(params.VersionMinor),
|
|
||||||
Patch: uint32(params.VersionPatch),
|
|
||||||
}
|
|
||||||
commit, _ := hex.DecodeString(gitCommit)
|
|
||||||
copy(config.Commit[:], commit)
|
|
||||||
return release.NewReleaseService(ctx, config)
|
|
||||||
}); err != nil {
|
|
||||||
utils.Fatalf("Failed to register the Geth release oracle service: %v", err)
|
|
||||||
}
|
|
||||||
return stack
|
|
||||||
}
|
|
||||||
|
|
||||||
// startNode boots up the system node and all registered protocols, after which
|
// startNode boots up the system node and all registered protocols, after which
|
||||||
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
||||||
// miner.
|
// miner.
|
||||||
|
|
@ -289,10 +259,12 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
}()
|
}()
|
||||||
// Start auxiliary services if enabled
|
// Start auxiliary services if enabled
|
||||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
|
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
|
||||||
|
// Mining only makes sense if a full Ethereum node is running
|
||||||
var ethereum *eth.Ethereum
|
var ethereum *eth.Ethereum
|
||||||
if err := stack.Service(ðereum); err != nil {
|
if err := stack.Service(ðereum); err != nil {
|
||||||
utils.Fatalf("ethereum service not running: %v", err)
|
utils.Fatalf("ethereum service not running: %v", err)
|
||||||
}
|
}
|
||||||
|
// Use a reduced number of threads if requested
|
||||||
if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
|
if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
|
||||||
type threaded interface {
|
type threaded interface {
|
||||||
SetThreads(threads int)
|
SetThreads(threads int)
|
||||||
|
|
@ -301,7 +273,9 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
th.SetThreads(threads)
|
th.SetThreads(threads)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := ethereum.StartMining(); err != nil {
|
// Set the gas price to the limits from the CLI and start mining
|
||||||
|
ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
|
||||||
|
if err := ethereum.StartMining(true); err != nil {
|
||||||
utils.Fatalf("Failed to start mining: %v", err)
|
utils.Fatalf("Failed to start mining: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
makedagCommand = cli.Command{
|
makedagCommand = cli.Command{
|
||||||
Action: makedag,
|
Action: utils.MigrateFlags(makedag),
|
||||||
Name: "makedag",
|
Name: "makedag",
|
||||||
Usage: "Generate ethash DAG (for testing)",
|
Usage: "Generate ethash DAG (for testing)",
|
||||||
ArgsUsage: "<blockNum> <outputDir>",
|
ArgsUsage: "<blockNum> <outputDir>",
|
||||||
|
|
@ -47,7 +47,7 @@ Regular users do not need to execute it.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
versionCommand = cli.Command{
|
versionCommand = cli.Command{
|
||||||
Action: version,
|
Action: utils.MigrateFlags(version),
|
||||||
Name: "version",
|
Name: "version",
|
||||||
Usage: "Print version numbers",
|
Usage: "Print version numbers",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
|
|
@ -57,7 +57,7 @@ The output of this command is supposed to be machine-readable.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
licenseCommand = cli.Command{
|
licenseCommand = cli.Command{
|
||||||
Action: license,
|
Action: utils.MigrateFlags(license),
|
||||||
Name: "license",
|
Name: "license",
|
||||||
Usage: "Display license information",
|
Usage: "Display license information",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
|
|
@ -103,7 +103,7 @@ func version(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
fmt.Println("Architecture:", runtime.GOARCH)
|
fmt.Println("Architecture:", runtime.GOARCH)
|
||||||
fmt.Println("Protocol Versions:", eth.ProtocolVersions)
|
fmt.Println("Protocol Versions:", eth.ProtocolVersions)
|
||||||
fmt.Println("Network Id:", ctx.GlobalInt(utils.NetworkIdFlag.Name))
|
fmt.Println("Network Id:", eth.DefaultConfig.NetworkId)
|
||||||
fmt.Println("Go Version:", runtime.Version())
|
fmt.Println("Go Version:", runtime.Version())
|
||||||
fmt.Println("Operating System:", runtime.GOOS)
|
fmt.Println("Operating System:", runtime.GOOS)
|
||||||
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ var (
|
||||||
Usage: "Refresh interval in seconds",
|
Usage: "Refresh interval in seconds",
|
||||||
}
|
}
|
||||||
monitorCommand = cli.Command{
|
monitorCommand = cli.Command{
|
||||||
Action: monitor,
|
Action: utils.MigrateFlags(monitor), // keep track of migration progress
|
||||||
Name: "monitor",
|
Name: "monitor",
|
||||||
Usage: "Monitor and visualize node metrics",
|
Usage: "Monitor and visualize node metrics",
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
|
|
|
||||||
|
|
@ -17,18 +17,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
|
||||||
"time"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tmpdir(t *testing.T) string {
|
func tmpdir(t *testing.T) string {
|
||||||
|
|
@ -40,36 +35,37 @@ func tmpdir(t *testing.T) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type testgeth struct {
|
type testgeth struct {
|
||||||
// For total convenience, all testing methods are available.
|
*cmdtest.TestCmd
|
||||||
*testing.T
|
|
||||||
// template variables for expect
|
// template variables for expect
|
||||||
Datadir string
|
Datadir string
|
||||||
Executable string
|
|
||||||
Etherbase string
|
Etherbase string
|
||||||
Func template.FuncMap
|
|
||||||
|
|
||||||
removeDatadir bool
|
|
||||||
cmd *exec.Cmd
|
|
||||||
stdout *bufio.Reader
|
|
||||||
stdin io.WriteCloser
|
|
||||||
stderr *testlogger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Run the app if we're the child process for runGeth.
|
// Run the app if we've been exec'd as "geth-test" in runGeth.
|
||||||
if os.Getenv("GETH_TEST_CHILD") != "" {
|
reexec.Register("geth-test", func() {
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := app.Run(os.Args); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// check if we have been reexec'd
|
||||||
|
if reexec.Init() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
}
|
}
|
||||||
|
|
||||||
// spawns geth with the given command line args. If the args don't set --datadir, the
|
// spawns geth with the given command line args. If the args don't set --datadir, the
|
||||||
// child g gets a temporary data directory.
|
// child g gets a temporary data directory.
|
||||||
func runGeth(t *testing.T, args ...string) *testgeth {
|
func runGeth(t *testing.T, args ...string) *testgeth {
|
||||||
tt := &testgeth{T: t, Executable: os.Args[0]}
|
tt := &testgeth{}
|
||||||
|
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
switch {
|
switch {
|
||||||
case arg == "-datadir" || arg == "--datadir":
|
case arg == "-datadir" || arg == "--datadir":
|
||||||
|
|
@ -84,215 +80,19 @@ func runGeth(t *testing.T, args ...string) *testgeth {
|
||||||
}
|
}
|
||||||
if tt.Datadir == "" {
|
if tt.Datadir == "" {
|
||||||
tt.Datadir = tmpdir(t)
|
tt.Datadir = tmpdir(t)
|
||||||
tt.removeDatadir = true
|
tt.Cleanup = func() { os.RemoveAll(tt.Datadir) }
|
||||||
args = append([]string{"-datadir", tt.Datadir}, args...)
|
args = append([]string{"-datadir", tt.Datadir}, args...)
|
||||||
// Remove the temporary datadir if something fails below.
|
// Remove the temporary datadir if something fails below.
|
||||||
defer func() {
|
defer func() {
|
||||||
if t.Failed() {
|
if t.Failed() {
|
||||||
os.RemoveAll(tt.Datadir)
|
tt.Cleanup()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boot "geth". This actually runs the test binary but the init function
|
// Boot "geth". This actually runs the test binary but the TestMain
|
||||||
// will prevent any tests from running.
|
// function will prevent any tests from running.
|
||||||
tt.stderr = &testlogger{t: t}
|
tt.Run("geth-test", args...)
|
||||||
tt.cmd = exec.Command(os.Args[0], args...)
|
|
||||||
tt.cmd.Env = append(os.Environ(), "GETH_TEST_CHILD=1")
|
|
||||||
tt.cmd.Stderr = tt.stderr
|
|
||||||
stdout, err := tt.cmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tt.stdout = bufio.NewReader(stdout)
|
|
||||||
if tt.stdin, err = tt.cmd.StdinPipe(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := tt.cmd.Start(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return tt
|
return tt
|
||||||
}
|
}
|
||||||
|
|
||||||
// InputLine writes the given text to the childs stdin.
|
|
||||||
// This method can also be called from an expect template, e.g.:
|
|
||||||
//
|
|
||||||
// geth.expect(`Passphrase: {{.InputLine "password"}}`)
|
|
||||||
func (tt *testgeth) InputLine(s string) string {
|
|
||||||
io.WriteString(tt.stdin, s+"\n")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tt *testgeth) setTemplateFunc(name string, fn interface{}) {
|
|
||||||
if tt.Func == nil {
|
|
||||||
tt.Func = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
tt.Func[name] = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
// expect runs its argument as a template, then expects the
|
|
||||||
// child process to output the result of the template within 5s.
|
|
||||||
//
|
|
||||||
// If the template starts with a newline, the newline is removed
|
|
||||||
// before matching.
|
|
||||||
func (tt *testgeth) expect(tplsource string) {
|
|
||||||
// Generate the expected output by running the template.
|
|
||||||
tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource))
|
|
||||||
wantbuf := new(bytes.Buffer)
|
|
||||||
if err := tpl.Execute(wantbuf, tt); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Trim exactly one newline at the beginning. This makes tests look
|
|
||||||
// much nicer because all expect strings are at column 0.
|
|
||||||
want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n"))
|
|
||||||
if err := tt.matchExactOutput(want); err != nil {
|
|
||||||
tt.Fatal(err)
|
|
||||||
}
|
|
||||||
tt.Logf("Matched stdout text:\n%s", want)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tt *testgeth) matchExactOutput(want []byte) error {
|
|
||||||
buf := make([]byte, len(want))
|
|
||||||
n := 0
|
|
||||||
tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) })
|
|
||||||
buf = buf[:n]
|
|
||||||
if n < len(want) || !bytes.Equal(buf, want) {
|
|
||||||
// Grab any additional buffered output in case of mismatch
|
|
||||||
// because it might help with debugging.
|
|
||||||
buf = append(buf, make([]byte, tt.stdout.Buffered())...)
|
|
||||||
tt.stdout.Read(buf[n:])
|
|
||||||
// Find the mismatch position.
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
if want[i] != buf[i] {
|
|
||||||
return fmt.Errorf("Output mismatch at ◊:\n---------------- (stdout text)\n%s◊%s\n---------------- (expected text)\n%s",
|
|
||||||
buf[:i], buf[i:n], want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n < len(want) {
|
|
||||||
return fmt.Errorf("Not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s",
|
|
||||||
buf, want[:n], want[n:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectRegexp expects the child process to output text matching the
|
|
||||||
// given regular expression within 5s.
|
|
||||||
//
|
|
||||||
// Note that an arbitrary amount of output may be consumed by the
|
|
||||||
// regular expression. This usually means that expect cannot be used
|
|
||||||
// after expectRegexp.
|
|
||||||
func (tt *testgeth) expectRegexp(resource string) (*regexp.Regexp, []string) {
|
|
||||||
var (
|
|
||||||
re = regexp.MustCompile(resource)
|
|
||||||
rtee = &runeTee{in: tt.stdout}
|
|
||||||
matches []int
|
|
||||||
)
|
|
||||||
tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) })
|
|
||||||
output := rtee.buf.Bytes()
|
|
||||||
if matches == nil {
|
|
||||||
tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s",
|
|
||||||
output, resource)
|
|
||||||
return re, nil
|
|
||||||
}
|
|
||||||
tt.Logf("Matched stdout text:\n%s", output)
|
|
||||||
var submatch []string
|
|
||||||
for i := 0; i < len(matches); i += 2 {
|
|
||||||
submatch = append(submatch, string(output[i:i+1]))
|
|
||||||
}
|
|
||||||
return re, submatch
|
|
||||||
}
|
|
||||||
|
|
||||||
// expectExit expects the child process to exit within 5s without
|
|
||||||
// printing any additional text on stdout.
|
|
||||||
func (tt *testgeth) expectExit() {
|
|
||||||
var output []byte
|
|
||||||
tt.withKillTimeout(func() {
|
|
||||||
output, _ = ioutil.ReadAll(tt.stdout)
|
|
||||||
})
|
|
||||||
tt.cmd.Wait()
|
|
||||||
if tt.removeDatadir {
|
|
||||||
os.RemoveAll(tt.Datadir)
|
|
||||||
}
|
|
||||||
if len(output) > 0 {
|
|
||||||
tt.Errorf("Unmatched stdout text:\n%s", output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tt *testgeth) interrupt() {
|
|
||||||
tt.cmd.Process.Signal(os.Interrupt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// stderrText returns any stderr output written so far.
|
|
||||||
// The returned text holds all log lines after expectExit has
|
|
||||||
// returned.
|
|
||||||
func (tt *testgeth) stderrText() string {
|
|
||||||
tt.stderr.mu.Lock()
|
|
||||||
defer tt.stderr.mu.Unlock()
|
|
||||||
return tt.stderr.buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tt *testgeth) withKillTimeout(fn func()) {
|
|
||||||
timeout := time.AfterFunc(5*time.Second, func() {
|
|
||||||
tt.Log("killing the child process (timeout)")
|
|
||||||
tt.cmd.Process.Kill()
|
|
||||||
if tt.removeDatadir {
|
|
||||||
os.RemoveAll(tt.Datadir)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
defer timeout.Stop()
|
|
||||||
fn()
|
|
||||||
}
|
|
||||||
|
|
||||||
// testlogger logs all written lines via t.Log and also
|
|
||||||
// collects them for later inspection.
|
|
||||||
type testlogger struct {
|
|
||||||
t *testing.T
|
|
||||||
mu sync.Mutex
|
|
||||||
buf bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tl *testlogger) Write(b []byte) (n int, err error) {
|
|
||||||
lines := bytes.Split(b, []byte("\n"))
|
|
||||||
for _, line := range lines {
|
|
||||||
if len(line) > 0 {
|
|
||||||
tl.t.Logf("(stderr) %s", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tl.mu.Lock()
|
|
||||||
tl.buf.Write(b)
|
|
||||||
tl.mu.Unlock()
|
|
||||||
return len(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// runeTee collects text read through it into buf.
|
|
||||||
type runeTee struct {
|
|
||||||
in interface {
|
|
||||||
io.Reader
|
|
||||||
io.ByteReader
|
|
||||||
io.RuneReader
|
|
||||||
}
|
|
||||||
buf bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rtee *runeTee) Read(b []byte) (n int, err error) {
|
|
||||||
n, err = rtee.in.Read(b)
|
|
||||||
rtee.buf.Write(b[:n])
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rtee *runeTee) ReadRune() (r rune, size int, err error) {
|
|
||||||
r, size, err = rtee.in.ReadRune()
|
|
||||||
if err == nil {
|
|
||||||
rtee.buf.WriteRune(r)
|
|
||||||
}
|
|
||||||
return r, size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rtee *runeTee) ReadByte() (b byte, err error) {
|
|
||||||
b, err = rtee.in.ReadByte()
|
|
||||||
if err == nil {
|
|
||||||
rtee.buf.WriteByte(b)
|
|
||||||
}
|
|
||||||
return b, err
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
|
|
@ -30,7 +31,7 @@ import (
|
||||||
var AppHelpTemplate = `NAME:
|
var AppHelpTemplate = `NAME:
|
||||||
{{.App.Name}} - {{.App.Usage}}
|
{{.App.Name}} - {{.App.Usage}}
|
||||||
|
|
||||||
Copyright 2013-2016 The go-ethereum Authors
|
Copyright 2013-2017 The go-ethereum Authors
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
|
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
|
|
@ -64,14 +65,17 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
{
|
{
|
||||||
Name: "ETHEREUM",
|
Name: "ETHEREUM",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
configFileFlag,
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.KeyStoreDirFlag,
|
utils.KeyStoreDirFlag,
|
||||||
|
utils.NoUSBFlag,
|
||||||
utils.NetworkIdFlag,
|
utils.NetworkIdFlag,
|
||||||
utils.TestNetFlag,
|
utils.TestnetFlag,
|
||||||
|
utils.RinkebyFlag,
|
||||||
utils.DevModeFlag,
|
utils.DevModeFlag,
|
||||||
|
utils.SyncModeFlag,
|
||||||
|
utils.EthStatsURLFlag,
|
||||||
utils.IdentityFlag,
|
utils.IdentityFlag,
|
||||||
utils.FastSyncFlag,
|
|
||||||
utils.LightModeFlag,
|
|
||||||
utils.LightServFlag,
|
utils.LightServFlag,
|
||||||
utils.LightPeersFlag,
|
utils.LightPeersFlag,
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
|
|
@ -88,6 +92,19 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.EthashDatasetsOnDiskFlag,
|
utils.EthashDatasetsOnDiskFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "TRANSACTION POOL",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.TxPoolNoLocalsFlag,
|
||||||
|
utils.TxPoolPriceLimitFlag,
|
||||||
|
utils.TxPoolPriceBumpFlag,
|
||||||
|
utils.TxPoolAccountSlotsFlag,
|
||||||
|
utils.TxPoolGlobalSlotsFlag,
|
||||||
|
utils.TxPoolAccountQueueFlag,
|
||||||
|
utils.TxPoolGlobalQueueFlag,
|
||||||
|
utils.TxPoolLifetimeFlag,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "PERFORMANCE TUNING",
|
Name: "PERFORMANCE TUNING",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
|
@ -115,7 +132,6 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.WSApiFlag,
|
utils.WSApiFlag,
|
||||||
utils.WSAllowedOriginsFlag,
|
utils.WSAllowedOriginsFlag,
|
||||||
utils.IPCDisabledFlag,
|
utils.IPCDisabledFlag,
|
||||||
utils.IPCApiFlag,
|
|
||||||
utils.IPCPathFlag,
|
utils.IPCPathFlag,
|
||||||
utils.RPCCORSDomainFlag,
|
utils.RPCCORSDomainFlag,
|
||||||
utils.JSpathFlag,
|
utils.JSpathFlag,
|
||||||
|
|
@ -127,12 +143,15 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
Name: "NETWORKING",
|
Name: "NETWORKING",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.BootnodesFlag,
|
utils.BootnodesFlag,
|
||||||
|
utils.BootnodesV4Flag,
|
||||||
|
utils.BootnodesV5Flag,
|
||||||
utils.ListenPortFlag,
|
utils.ListenPortFlag,
|
||||||
utils.MaxPeersFlag,
|
utils.MaxPeersFlag,
|
||||||
utils.MaxPendingPeersFlag,
|
utils.MaxPendingPeersFlag,
|
||||||
utils.NATFlag,
|
utils.NATFlag,
|
||||||
utils.NoDiscoverFlag,
|
utils.NoDiscoverFlag,
|
||||||
utils.DiscoveryV5Flag,
|
utils.DiscoveryV5Flag,
|
||||||
|
utils.NetrestrictFlag,
|
||||||
utils.NodeKeyFileFlag,
|
utils.NodeKeyFileFlag,
|
||||||
utils.NodeKeyHexFlag,
|
utils.NodeKeyHexFlag,
|
||||||
},
|
},
|
||||||
|
|
@ -158,34 +177,66 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
{
|
{
|
||||||
Name: "VIRTUAL MACHINE",
|
Name: "VIRTUAL MACHINE",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.VMEnableJitFlag,
|
|
||||||
utils.VMForceJitFlag,
|
|
||||||
utils.VMJitCacheFlag,
|
|
||||||
utils.VMEnableDebugFlag,
|
utils.VMEnableDebugFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "LOGGING AND DEBUGGING",
|
Name: "LOGGING AND DEBUGGING",
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
utils.EthStatsURLFlag,
|
|
||||||
utils.MetricsEnabledFlag,
|
utils.MetricsEnabledFlag,
|
||||||
utils.FakePoWFlag,
|
utils.FakePoWFlag,
|
||||||
|
utils.NoCompactionFlag,
|
||||||
}, debug.Flags...),
|
}, debug.Flags...),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "EXPERIMENTAL",
|
Name: "WHISPER (EXPERIMENTAL)",
|
||||||
|
Flags: whisperFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "DEPRECATED",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.WhisperEnabledFlag,
|
utils.FastSyncFlag,
|
||||||
|
utils.LightModeFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "MISCELLANEOUS",
|
Name: "MISC",
|
||||||
Flags: []cli.Flag{
|
|
||||||
utils.SolcPathFlag,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// byCategory sorts an array of flagGroup by Name in the order
|
||||||
|
// defined in AppHelpFlagGroups.
|
||||||
|
type byCategory []flagGroup
|
||||||
|
|
||||||
|
func (a byCategory) Len() int { return len(a) }
|
||||||
|
func (a byCategory) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byCategory) Less(i, j int) bool {
|
||||||
|
iCat, jCat := a[i].Name, a[j].Name
|
||||||
|
iIdx, jIdx := len(AppHelpFlagGroups), len(AppHelpFlagGroups) // ensure non categorized flags come last
|
||||||
|
|
||||||
|
for i, group := range AppHelpFlagGroups {
|
||||||
|
if iCat == group.Name {
|
||||||
|
iIdx = i
|
||||||
|
}
|
||||||
|
if jCat == group.Name {
|
||||||
|
jIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iIdx < jIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagCategory(flag cli.Flag) string {
|
||||||
|
for _, category := range AppHelpFlagGroups {
|
||||||
|
for _, flg := range category.Flags {
|
||||||
|
if flg.GetName() == flag.GetName() {
|
||||||
|
return category.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "MISC"
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Override the default app help template
|
// Override the default app help template
|
||||||
cli.AppHelpTemplate = AppHelpTemplate
|
cli.AppHelpTemplate = AppHelpTemplate
|
||||||
|
|
@ -195,6 +246,7 @@ func init() {
|
||||||
App interface{}
|
App interface{}
|
||||||
FlagGroups []flagGroup
|
FlagGroups []flagGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override the default app help printer, but only for the global app help
|
// Override the default app help printer, but only for the global app help
|
||||||
originalHelpPrinter := cli.HelpPrinter
|
originalHelpPrinter := cli.HelpPrinter
|
||||||
cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
|
cli.HelpPrinter = func(w io.Writer, tmpl string, data interface{}) {
|
||||||
|
|
@ -224,6 +276,27 @@ func init() {
|
||||||
}
|
}
|
||||||
// Render out custom usage screen
|
// Render out custom usage screen
|
||||||
originalHelpPrinter(w, tmpl, helpData{data, AppHelpFlagGroups})
|
originalHelpPrinter(w, tmpl, helpData{data, AppHelpFlagGroups})
|
||||||
|
} else if tmpl == utils.CommandHelpTemplate {
|
||||||
|
// Iterate over all command specific flags and categorize them
|
||||||
|
categorized := make(map[string][]cli.Flag)
|
||||||
|
for _, flag := range data.(cli.Command).Flags {
|
||||||
|
if _, ok := categorized[flag.String()]; !ok {
|
||||||
|
categorized[flagCategory(flag)] = append(categorized[flagCategory(flag)], flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort to get a stable ordering
|
||||||
|
sorted := make([]flagGroup, 0, len(categorized))
|
||||||
|
for cat, flgs := range categorized {
|
||||||
|
sorted = append(sorted, flagGroup{cat, flgs})
|
||||||
|
}
|
||||||
|
sort.Sort(byCategory(sorted))
|
||||||
|
|
||||||
|
// add sorted array to data and render with default printer
|
||||||
|
originalHelpPrinter(w, tmpl, map[string]interface{}{
|
||||||
|
"cmd": data,
|
||||||
|
"categorizedFlags": sorted,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
originalHelpPrinter(w, tmpl, data)
|
originalHelpPrinter(w, tmpl, data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
152
cmd/puppeth/module.go
Normal file
152
cmd/puppeth/module.go
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrServiceUnknown is returned when a service container doesn't exist.
|
||||||
|
ErrServiceUnknown = errors.New("service unknown")
|
||||||
|
|
||||||
|
// ErrServiceOffline is returned when a service container exists, but it is not
|
||||||
|
// running.
|
||||||
|
ErrServiceOffline = errors.New("service offline")
|
||||||
|
|
||||||
|
// ErrServiceUnreachable is returned when a service container is running, but
|
||||||
|
// seems to not respond to communication attempts.
|
||||||
|
ErrServiceUnreachable = errors.New("service unreachable")
|
||||||
|
|
||||||
|
// ErrNotExposed is returned if a web-service doesn't have an exposed port, nor
|
||||||
|
// a reverse-proxy in front of it to forward requests.
|
||||||
|
ErrNotExposed = errors.New("service not exposed, nor proxied")
|
||||||
|
)
|
||||||
|
|
||||||
|
// containerInfos is a heavily reduced version of the huge inspection dataset
|
||||||
|
// returned from docker inspect, parsed into a form easily usable by puppeth.
|
||||||
|
type containerInfos struct {
|
||||||
|
running bool // Flag whether the container is running currently
|
||||||
|
envvars map[string]string // Collection of environmental variables set on the container
|
||||||
|
portmap map[string]int // Port mapping from internal port/proto combos to host binds
|
||||||
|
volumes map[string]string // Volume mount points from container to host directories
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspectContainer runs docker inspect against a running container
|
||||||
|
func inspectContainer(client *sshClient, container string) (*containerInfos, error) {
|
||||||
|
// Check whether there's a container running for the service
|
||||||
|
out, err := client.Run(fmt.Sprintf("docker inspect %s", container))
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrServiceUnknown
|
||||||
|
}
|
||||||
|
// If yes, extract various configuration options
|
||||||
|
type inspection struct {
|
||||||
|
State struct {
|
||||||
|
Running bool
|
||||||
|
}
|
||||||
|
Mounts []struct {
|
||||||
|
Source string
|
||||||
|
Destination string
|
||||||
|
}
|
||||||
|
Config struct {
|
||||||
|
Env []string
|
||||||
|
}
|
||||||
|
HostConfig struct {
|
||||||
|
PortBindings map[string][]map[string]string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var inspects []inspection
|
||||||
|
if err = json.Unmarshal(out, &inspects); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inspect := inspects[0]
|
||||||
|
|
||||||
|
// Infos retrieved, parse the above into something meaningful
|
||||||
|
infos := &containerInfos{
|
||||||
|
running: inspect.State.Running,
|
||||||
|
envvars: make(map[string]string),
|
||||||
|
portmap: make(map[string]int),
|
||||||
|
volumes: make(map[string]string),
|
||||||
|
}
|
||||||
|
for _, envvar := range inspect.Config.Env {
|
||||||
|
if parts := strings.Split(envvar, "="); len(parts) == 2 {
|
||||||
|
infos.envvars[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for portname, details := range inspect.HostConfig.PortBindings {
|
||||||
|
if len(details) > 0 {
|
||||||
|
port, _ := strconv.Atoi(details[0]["HostPort"])
|
||||||
|
infos.portmap[portname] = port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, mount := range inspect.Mounts {
|
||||||
|
infos.volumes[mount.Destination] = mount.Source
|
||||||
|
}
|
||||||
|
return infos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// tearDown connects to a remote machine via SSH and terminates docker containers
|
||||||
|
// running with the specified name in the specified network.
|
||||||
|
func tearDown(client *sshClient, network string, service string, purge bool) ([]byte, error) {
|
||||||
|
// Tear down the running (or paused) container
|
||||||
|
out, err := client.Run(fmt.Sprintf("docker rm -f %s_%s_1", network, service))
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
// If requested, purge the associated docker image too
|
||||||
|
if purge {
|
||||||
|
return client.Run(fmt.Sprintf("docker rmi %s/%s", network, service))
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve retrieves the hostname a service is running on either by returning the
|
||||||
|
// actual server name and port, or preferably an nginx virtual host if available.
|
||||||
|
func resolve(client *sshClient, network string, service string, port int) (string, error) {
|
||||||
|
// Inspect the service to get various configurations from it
|
||||||
|
infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, service))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !infos.running {
|
||||||
|
return "", ErrServiceOffline
|
||||||
|
}
|
||||||
|
// Container online, extract any environmental variables
|
||||||
|
if vhost := infos.envvars["VIRTUAL_HOST"]; vhost != "" {
|
||||||
|
return vhost, nil
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", client.server, port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPort tries to connect to a remote host on a given
|
||||||
|
func checkPort(host string, port int) error {
|
||||||
|
log.Trace("Verifying remote TCP connectivity", "server", host, "port", port)
|
||||||
|
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Second)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
542
cmd/puppeth/module_dashboard.go
Normal file
542
cmd/puppeth/module_dashboard.go
Normal file
File diff suppressed because one or more lines are too long
164
cmd/puppeth/module_ethstats.go
Normal file
164
cmd/puppeth/module_ethstats.go
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ethstatsDockerfile is the Dockerfile required to build an ethstats backend
|
||||||
|
// and associated monitoring site.
|
||||||
|
var ethstatsDockerfile = `
|
||||||
|
FROM mhart/alpine-node:latest
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apk add --update git && \
|
||||||
|
git clone --depth=1 https://github.com/karalabe/eth-netstats && \
|
||||||
|
apk del git && rm -rf /var/cache/apk/* && \
|
||||||
|
\
|
||||||
|
cd /eth-netstats && npm install && npm install -g grunt-cli && grunt
|
||||||
|
|
||||||
|
WORKDIR /eth-netstats
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: []};' > lib/utils/config.js
|
||||||
|
|
||||||
|
CMD ["npm", "start"]
|
||||||
|
`
|
||||||
|
|
||||||
|
// ethstatsComposefile is the docker-compose.yml file required to deploy and
|
||||||
|
// maintain an ethstats monitoring site.
|
||||||
|
var ethstatsComposefile = `
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
ethstats:
|
||||||
|
build: .
|
||||||
|
image: {{.Network}}/ethstats{{if not .VHost}}
|
||||||
|
ports:
|
||||||
|
- "{{.Port}}:3000"{{end}}
|
||||||
|
environment:
|
||||||
|
- WS_SECRET={{.Secret}}{{if .VHost}}
|
||||||
|
- VIRTUAL_HOST={{.VHost}}{{end}}
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "1m"
|
||||||
|
max-file: "10"
|
||||||
|
restart: always
|
||||||
|
`
|
||||||
|
|
||||||
|
// deployEthstats deploys a new ethstats container to a remote machine via SSH,
|
||||||
|
// docker and docker-compose. If an instance with the specified network name
|
||||||
|
// already exists there, it will be overwritten!
|
||||||
|
func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string) ([]byte, error) {
|
||||||
|
// Generate the content to upload to the server
|
||||||
|
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||||
|
files := make(map[string][]byte)
|
||||||
|
|
||||||
|
for i, address := range trusted {
|
||||||
|
trusted[i] = fmt.Sprintf("\"%s\"", address)
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerfile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(ethstatsDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||||
|
"Trusted": strings.Join(trusted, ", "),
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||||
|
|
||||||
|
composefile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(ethstatsComposefile)).Execute(composefile, map[string]interface{}{
|
||||||
|
"Network": network,
|
||||||
|
"Port": port,
|
||||||
|
"Secret": secret,
|
||||||
|
"VHost": vhost,
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
|
// Upload the deployment files to the remote server (and clean up afterwards)
|
||||||
|
if out, err := client.Upload(files); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer client.Run("rm -rf " + workdir)
|
||||||
|
|
||||||
|
// Build and deploy the ethstats service
|
||||||
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build", workdir, network))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ethstatsInfos is returned from an ethstats status check to allow reporting
|
||||||
|
// various configuration parameters.
|
||||||
|
type ethstatsInfos struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
secret string
|
||||||
|
config string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the stringer interface.
|
||||||
|
func (info *ethstatsInfos) String() string {
|
||||||
|
return fmt.Sprintf("host=%s, port=%d, secret=%s", info.host, info.port, info.secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkEthstats does a health-check against an ethstats server to verify whether
|
||||||
|
// it's running, and if yes, gathering a collection of useful infos about it.
|
||||||
|
func checkEthstats(client *sshClient, network string) (*ethstatsInfos, error) {
|
||||||
|
// Inspect a possible ethstats container on the host
|
||||||
|
infos, err := inspectContainer(client, fmt.Sprintf("%s_ethstats_1", network))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !infos.running {
|
||||||
|
return nil, ErrServiceOffline
|
||||||
|
}
|
||||||
|
// Resolve the port from the host, or the reverse proxy
|
||||||
|
port := infos.portmap["3000/tcp"]
|
||||||
|
if port == 0 {
|
||||||
|
if proxy, _ := checkNginx(client, network); proxy != nil {
|
||||||
|
port = proxy.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if port == 0 {
|
||||||
|
return nil, ErrNotExposed
|
||||||
|
}
|
||||||
|
// Resolve the host from the reverse-proxy and configure the connection string
|
||||||
|
host := infos.envvars["VIRTUAL_HOST"]
|
||||||
|
if host == "" {
|
||||||
|
host = client.server
|
||||||
|
}
|
||||||
|
secret := infos.envvars["WS_SECRET"]
|
||||||
|
config := fmt.Sprintf("%s@%s", secret, host)
|
||||||
|
if port != 80 && port != 443 {
|
||||||
|
config += fmt.Sprintf(":%d", port)
|
||||||
|
}
|
||||||
|
// Run a sanity check to see if the port is reachable
|
||||||
|
if err = checkPort(host, port); err != nil {
|
||||||
|
log.Warn("Ethstats service seems unreachable", "server", host, "port", port, "err", err)
|
||||||
|
}
|
||||||
|
// Container available, assemble and return the useful infos
|
||||||
|
return ðstatsInfos{
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
secret: secret,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
232
cmd/puppeth/module_faucet.go
Normal file
232
cmd/puppeth/module_faucet.go
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// faucetDockerfile is the Dockerfile required to build an faucet container to
|
||||||
|
// grant crypto tokens based on GitHub authentications.
|
||||||
|
var faucetDockerfile = `
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
RUN mkdir /go
|
||||||
|
ENV GOPATH /go
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apk add --update git go make gcc musl-dev ca-certificates linux-headers && \
|
||||||
|
mkdir -p $GOPATH/src/github.com/ethereum && \
|
||||||
|
(cd $GOPATH/src/github.com/ethereum && git clone --depth=1 https://github.com/ethereum/go-ethereum) && \
|
||||||
|
go build -v github.com/ethereum/go-ethereum/cmd/faucet && \
|
||||||
|
apk del git go make gcc musl-dev linux-headers && \
|
||||||
|
rm -rf $GOPATH && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
ADD genesis.json /genesis.json
|
||||||
|
ADD account.json /account.json
|
||||||
|
ADD account.pass /account.pass
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD [ \
|
||||||
|
"/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
|
||||||
|
"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
|
||||||
|
"--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", "--account.json", "/account.json", "--account.pass", "/account.pass" \
|
||||||
|
{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}} \
|
||||||
|
]`
|
||||||
|
|
||||||
|
// faucetComposefile is the docker-compose.yml file required to deploy and maintain
|
||||||
|
// a crypto faucet.
|
||||||
|
var faucetComposefile = `
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
faucet:
|
||||||
|
build: .
|
||||||
|
image: {{.Network}}/faucet
|
||||||
|
ports:
|
||||||
|
- "{{.EthPort}}:{{.EthPort}}"{{if not .VHost}}
|
||||||
|
- "{{.ApiPort}}:8080"{{end}}
|
||||||
|
volumes:
|
||||||
|
- {{.Datadir}}:/root/.faucet
|
||||||
|
environment:
|
||||||
|
- ETH_PORT={{.EthPort}}
|
||||||
|
- ETH_NAME={{.EthName}}
|
||||||
|
- FAUCET_AMOUNT={{.FaucetAmount}}
|
||||||
|
- FAUCET_MINUTES={{.FaucetMinutes}}
|
||||||
|
- FAUCET_TIERS={{.FaucetTiers}}
|
||||||
|
- GITHUB_USER={{.GitHubUser}}
|
||||||
|
- GITHUB_TOKEN={{.GitHubToken}}
|
||||||
|
- CAPTCHA_TOKEN={{.CaptchaToken}}
|
||||||
|
- CAPTCHA_SECRET={{.CaptchaSecret}}{{if .VHost}}
|
||||||
|
- VIRTUAL_HOST={{.VHost}}
|
||||||
|
- VIRTUAL_PORT=8080{{end}}
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "1m"
|
||||||
|
max-file: "10"
|
||||||
|
restart: always
|
||||||
|
`
|
||||||
|
|
||||||
|
// deployFaucet deploys a new faucet container to a remote machine via SSH,
|
||||||
|
// docker and docker-compose. If an instance with the specified network name
|
||||||
|
// already exists there, it will be overwritten!
|
||||||
|
func deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos) ([]byte, error) {
|
||||||
|
// Generate the content to upload to the server
|
||||||
|
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||||
|
files := make(map[string][]byte)
|
||||||
|
|
||||||
|
dockerfile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(faucetDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||||
|
"NetworkID": config.node.network,
|
||||||
|
"Bootnodes": strings.Join(bootnodes, ","),
|
||||||
|
"Ethstats": config.node.ethstats,
|
||||||
|
"EthPort": config.node.portFull,
|
||||||
|
"GitHubUser": config.githubUser,
|
||||||
|
"GitHubToken": config.githubToken,
|
||||||
|
"CaptchaToken": config.captchaToken,
|
||||||
|
"CaptchaSecret": config.captchaSecret,
|
||||||
|
"FaucetName": strings.Title(network),
|
||||||
|
"FaucetAmount": config.amount,
|
||||||
|
"FaucetMinutes": config.minutes,
|
||||||
|
"FaucetTiers": config.tiers,
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||||
|
|
||||||
|
composefile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(faucetComposefile)).Execute(composefile, map[string]interface{}{
|
||||||
|
"Network": network,
|
||||||
|
"Datadir": config.node.datadir,
|
||||||
|
"VHost": config.host,
|
||||||
|
"ApiPort": config.port,
|
||||||
|
"EthPort": config.node.portFull,
|
||||||
|
"EthName": config.node.ethstats[:strings.Index(config.node.ethstats, ":")],
|
||||||
|
"GitHubUser": config.githubUser,
|
||||||
|
"GitHubToken": config.githubToken,
|
||||||
|
"CaptchaToken": config.captchaToken,
|
||||||
|
"CaptchaSecret": config.captchaSecret,
|
||||||
|
"FaucetAmount": config.amount,
|
||||||
|
"FaucetMinutes": config.minutes,
|
||||||
|
"FaucetTiers": config.tiers,
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
|
files[filepath.Join(workdir, "genesis.json")] = []byte(config.node.genesis)
|
||||||
|
files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON)
|
||||||
|
files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass)
|
||||||
|
|
||||||
|
// Upload the deployment files to the remote server (and clean up afterwards)
|
||||||
|
if out, err := client.Upload(files); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer client.Run("rm -rf " + workdir)
|
||||||
|
|
||||||
|
// Build and deploy the faucet service
|
||||||
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build", workdir, network))
|
||||||
|
}
|
||||||
|
|
||||||
|
// faucetInfos is returned from an faucet status check to allow reporting various
|
||||||
|
// configuration parameters.
|
||||||
|
type faucetInfos struct {
|
||||||
|
node *nodeInfos
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
amount int
|
||||||
|
minutes int
|
||||||
|
tiers int
|
||||||
|
githubUser string
|
||||||
|
githubToken string
|
||||||
|
captchaToken string
|
||||||
|
captchaSecret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the stringer interface.
|
||||||
|
func (info *faucetInfos) String() string {
|
||||||
|
return fmt.Sprintf("host=%s, api=%d, eth=%d, amount=%d, minutes=%d, tiers=%d, github=%s, captcha=%v, ethstats=%s", info.host, info.port, info.node.portFull, info.amount, info.minutes, info.tiers, info.githubUser, info.captchaToken != "", info.node.ethstats)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkFaucet does a health-check against an faucet server to verify whether
|
||||||
|
// it's running, and if yes, gathering a collection of useful infos about it.
|
||||||
|
func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
||||||
|
// Inspect a possible faucet container on the host
|
||||||
|
infos, err := inspectContainer(client, fmt.Sprintf("%s_faucet_1", network))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !infos.running {
|
||||||
|
return nil, ErrServiceOffline
|
||||||
|
}
|
||||||
|
// Resolve the port from the host, or the reverse proxy
|
||||||
|
port := infos.portmap["8080/tcp"]
|
||||||
|
if port == 0 {
|
||||||
|
if proxy, _ := checkNginx(client, network); proxy != nil {
|
||||||
|
port = proxy.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if port == 0 {
|
||||||
|
return nil, ErrNotExposed
|
||||||
|
}
|
||||||
|
// Resolve the host from the reverse-proxy and the config values
|
||||||
|
host := infos.envvars["VIRTUAL_HOST"]
|
||||||
|
if host == "" {
|
||||||
|
host = client.server
|
||||||
|
}
|
||||||
|
amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"])
|
||||||
|
minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
|
||||||
|
tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"])
|
||||||
|
|
||||||
|
// Retrieve the funding account informations
|
||||||
|
var out []byte
|
||||||
|
keyJSON, keyPass := "", ""
|
||||||
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.json", network)); err == nil {
|
||||||
|
keyJSON = string(bytes.TrimSpace(out))
|
||||||
|
}
|
||||||
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.pass", network)); err == nil {
|
||||||
|
keyPass = string(bytes.TrimSpace(out))
|
||||||
|
}
|
||||||
|
// Run a sanity check to see if the port is reachable
|
||||||
|
if err = checkPort(host, port); err != nil {
|
||||||
|
log.Warn("Faucet service seems unreachable", "server", host, "port", port, "err", err)
|
||||||
|
}
|
||||||
|
// Container available, assemble and return the useful infos
|
||||||
|
return &faucetInfos{
|
||||||
|
node: &nodeInfos{
|
||||||
|
datadir: infos.volumes["/root/.faucet"],
|
||||||
|
portFull: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
|
||||||
|
ethstats: infos.envvars["ETH_NAME"],
|
||||||
|
keyJSON: keyJSON,
|
||||||
|
keyPass: keyPass,
|
||||||
|
},
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
amount: amount,
|
||||||
|
minutes: minutes,
|
||||||
|
tiers: tiers,
|
||||||
|
githubUser: infos.envvars["GITHUB_USER"],
|
||||||
|
githubToken: infos.envvars["GITHUB_TOKEN"],
|
||||||
|
captchaToken: infos.envvars["CAPTCHA_TOKEN"],
|
||||||
|
captchaSecret: infos.envvars["CAPTCHA_SECRET"],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
111
cmd/puppeth/module_nginx.go
Normal file
111
cmd/puppeth/module_nginx.go
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nginxDockerfile is theis the Dockerfile required to build an nginx reverse-
|
||||||
|
// proxy.
|
||||||
|
var nginxDockerfile = `FROM jwilder/nginx-proxy`
|
||||||
|
|
||||||
|
// nginxComposefile is the docker-compose.yml file required to deploy and maintain
|
||||||
|
// an nginx reverse-proxy. The proxy is responsible for exposing one or more HTTP
|
||||||
|
// services running on a single host.
|
||||||
|
var nginxComposefile = `
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
nginx:
|
||||||
|
build: .
|
||||||
|
image: {{.Network}}/nginx
|
||||||
|
ports:
|
||||||
|
- "{{.Port}}:80"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "1m"
|
||||||
|
max-file: "10"
|
||||||
|
restart: always
|
||||||
|
`
|
||||||
|
|
||||||
|
// deployNginx deploys a new nginx reverse-proxy container to expose one or more
|
||||||
|
// HTTP services running on a single host. If an instance with the specified
|
||||||
|
// network name already exists there, it will be overwritten!
|
||||||
|
func deployNginx(client *sshClient, network string, port int) ([]byte, error) {
|
||||||
|
log.Info("Deploying nginx reverse-proxy", "server", client.server, "port", port)
|
||||||
|
|
||||||
|
// Generate the content to upload to the server
|
||||||
|
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||||
|
files := make(map[string][]byte)
|
||||||
|
|
||||||
|
dockerfile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(nginxDockerfile)).Execute(dockerfile, nil)
|
||||||
|
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||||
|
|
||||||
|
composefile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(nginxComposefile)).Execute(composefile, map[string]interface{}{
|
||||||
|
"Network": network,
|
||||||
|
"Port": port,
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
|
// Upload the deployment files to the remote server (and clean up afterwards)
|
||||||
|
if out, err := client.Upload(files); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer client.Run("rm -rf " + workdir)
|
||||||
|
|
||||||
|
// Build and deploy the ethstats service
|
||||||
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build", workdir, network))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nginxInfos is returned from an nginx reverse-proxy status check to allow
|
||||||
|
// reporting various configuration parameters.
|
||||||
|
type nginxInfos struct {
|
||||||
|
port int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the stringer interface.
|
||||||
|
func (info *nginxInfos) String() string {
|
||||||
|
return fmt.Sprintf("port=%d", info.port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNginx does a health-check against an nginx reverse-proxy to verify whether
|
||||||
|
// it's running, and if yes, gathering a collection of useful infos about it.
|
||||||
|
func checkNginx(client *sshClient, network string) (*nginxInfos, error) {
|
||||||
|
// Inspect a possible nginx container on the host
|
||||||
|
infos, err := inspectContainer(client, fmt.Sprintf("%s_nginx_1", network))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !infos.running {
|
||||||
|
return nil, ErrServiceOffline
|
||||||
|
}
|
||||||
|
// Container available, assemble and return the useful infos
|
||||||
|
return &nginxInfos{
|
||||||
|
port: infos.portmap["80/tcp"],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
242
cmd/puppeth/module_node.go
Normal file
242
cmd/puppeth/module_node.go
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// nodeDockerfile is the Dockerfile required to run an Ethereum node.
|
||||||
|
var nodeDockerfile = `
|
||||||
|
FROM ethereum/client-go:alpine-develop
|
||||||
|
|
||||||
|
ADD genesis.json /genesis.json
|
||||||
|
{{if .Unlock}}
|
||||||
|
ADD signer.json /signer.json
|
||||||
|
ADD signer.pass /signer.pass
|
||||||
|
{{end}}
|
||||||
|
RUN \
|
||||||
|
echo '/geth init /genesis.json' > geth.sh && \{{if .Unlock}}
|
||||||
|
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
||||||
|
echo $'/geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .BootV4}}--bootnodesv4 {{.BootV4}}{{end}} {{if .BootV5}}--bootnodesv5 {{.BootV5}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine{{end}}{{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> geth.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
||||||
|
`
|
||||||
|
|
||||||
|
// nodeComposefile is the docker-compose.yml file required to deploy and maintain
|
||||||
|
// an Ethereum node (bootnode or miner for now).
|
||||||
|
var nodeComposefile = `
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
{{.Type}}:
|
||||||
|
build: .
|
||||||
|
image: {{.Network}}/{{.Type}}
|
||||||
|
ports:
|
||||||
|
- "{{.FullPort}}:{{.FullPort}}"
|
||||||
|
- "{{.FullPort}}:{{.FullPort}}/udp"{{if .Light}}
|
||||||
|
- "{{.LightPort}}:{{.LightPort}}/udp"{{end}}
|
||||||
|
volumes:
|
||||||
|
- {{.Datadir}}:/root/.ethereum
|
||||||
|
environment:
|
||||||
|
- FULL_PORT={{.FullPort}}/tcp
|
||||||
|
- LIGHT_PORT={{.LightPort}}/udp
|
||||||
|
- TOTAL_PEERS={{.TotalPeers}}
|
||||||
|
- LIGHT_PEERS={{.LightPeers}}
|
||||||
|
- STATS_NAME={{.Ethstats}}
|
||||||
|
- MINER_NAME={{.Etherbase}}
|
||||||
|
- GAS_TARGET={{.GasTarget}}
|
||||||
|
- GAS_PRICE={{.GasPrice}}
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "1m"
|
||||||
|
max-file: "10"
|
||||||
|
restart: always
|
||||||
|
`
|
||||||
|
|
||||||
|
// deployNode deploys a new Ethereum node container to a remote machine via SSH,
|
||||||
|
// docker and docker-compose. If an instance with the specified network name
|
||||||
|
// already exists there, it will be overwritten!
|
||||||
|
func deployNode(client *sshClient, network string, bootv4, bootv5 []string, config *nodeInfos) ([]byte, error) {
|
||||||
|
kind := "sealnode"
|
||||||
|
if config.keyJSON == "" && config.etherbase == "" {
|
||||||
|
kind = "bootnode"
|
||||||
|
bootv4 = make([]string, 0)
|
||||||
|
bootv5 = make([]string, 0)
|
||||||
|
}
|
||||||
|
// Generate the content to upload to the server
|
||||||
|
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||||
|
files := make(map[string][]byte)
|
||||||
|
|
||||||
|
lightFlag := ""
|
||||||
|
if config.peersLight > 0 {
|
||||||
|
lightFlag = fmt.Sprintf("--lightpeers=%d --lightserv=50", config.peersLight)
|
||||||
|
}
|
||||||
|
dockerfile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||||
|
"NetworkID": config.network,
|
||||||
|
"Port": config.portFull,
|
||||||
|
"Peers": config.peersTotal,
|
||||||
|
"LightFlag": lightFlag,
|
||||||
|
"BootV4": strings.Join(bootv4, ","),
|
||||||
|
"BootV5": strings.Join(bootv5, ","),
|
||||||
|
"Ethstats": config.ethstats,
|
||||||
|
"Etherbase": config.etherbase,
|
||||||
|
"GasTarget": uint64(1000000 * config.gasTarget),
|
||||||
|
"GasPrice": uint64(1000000000 * config.gasPrice),
|
||||||
|
"Unlock": config.keyJSON != "",
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||||
|
|
||||||
|
composefile := new(bytes.Buffer)
|
||||||
|
template.Must(template.New("").Parse(nodeComposefile)).Execute(composefile, map[string]interface{}{
|
||||||
|
"Type": kind,
|
||||||
|
"Datadir": config.datadir,
|
||||||
|
"Network": network,
|
||||||
|
"FullPort": config.portFull,
|
||||||
|
"TotalPeers": config.peersTotal,
|
||||||
|
"Light": config.peersLight > 0,
|
||||||
|
"LightPort": config.portFull + 1,
|
||||||
|
"LightPeers": config.peersLight,
|
||||||
|
"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
|
||||||
|
"Etherbase": config.etherbase,
|
||||||
|
"GasTarget": config.gasTarget,
|
||||||
|
"GasPrice": config.gasPrice,
|
||||||
|
})
|
||||||
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
|
//genesisfile, _ := json.MarshalIndent(config.genesis, "", " ")
|
||||||
|
files[filepath.Join(workdir, "genesis.json")] = []byte(config.genesis)
|
||||||
|
|
||||||
|
if config.keyJSON != "" {
|
||||||
|
files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON)
|
||||||
|
files[filepath.Join(workdir, "signer.pass")] = []byte(config.keyPass)
|
||||||
|
}
|
||||||
|
// Upload the deployment files to the remote server (and clean up afterwards)
|
||||||
|
if out, err := client.Upload(files); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer client.Run("rm -rf " + workdir)
|
||||||
|
|
||||||
|
// Build and deploy the boot or seal node service
|
||||||
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build", workdir, network))
|
||||||
|
}
|
||||||
|
|
||||||
|
// nodeInfos is returned from a boot or seal node status check to allow reporting
|
||||||
|
// various configuration parameters.
|
||||||
|
type nodeInfos struct {
|
||||||
|
genesis []byte
|
||||||
|
network int64
|
||||||
|
datadir string
|
||||||
|
ethstats string
|
||||||
|
portFull int
|
||||||
|
portLight int
|
||||||
|
enodeFull string
|
||||||
|
enodeLight string
|
||||||
|
peersTotal int
|
||||||
|
peersLight int
|
||||||
|
etherbase string
|
||||||
|
keyJSON string
|
||||||
|
keyPass string
|
||||||
|
gasTarget float64
|
||||||
|
gasPrice float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements the stringer interface.
|
||||||
|
func (info *nodeInfos) String() string {
|
||||||
|
discv5 := ""
|
||||||
|
if info.peersLight > 0 {
|
||||||
|
discv5 = fmt.Sprintf(", portv5=%d", info.portLight)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("port=%d%s, datadir=%s, peers=%d, lights=%d, ethstats=%s, gastarget=%0.3f MGas, gasprice=%0.3f GWei",
|
||||||
|
info.portFull, discv5, info.datadir, info.peersTotal, info.peersLight, info.ethstats, info.gasTarget, info.gasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkNode does a health-check against an boot or seal node server to verify
|
||||||
|
// whether it's running, and if yes, whether it's responsive.
|
||||||
|
func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) {
|
||||||
|
kind := "bootnode"
|
||||||
|
if !boot {
|
||||||
|
kind = "sealnode"
|
||||||
|
}
|
||||||
|
// Inspect a possible bootnode container on the host
|
||||||
|
infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, kind))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !infos.running {
|
||||||
|
return nil, ErrServiceOffline
|
||||||
|
}
|
||||||
|
// Resolve a few types from the environmental variables
|
||||||
|
totalPeers, _ := strconv.Atoi(infos.envvars["TOTAL_PEERS"])
|
||||||
|
lightPeers, _ := strconv.Atoi(infos.envvars["LIGHT_PEERS"])
|
||||||
|
gasTarget, _ := strconv.ParseFloat(infos.envvars["GAS_TARGET"], 64)
|
||||||
|
gasPrice, _ := strconv.ParseFloat(infos.envvars["GAS_PRICE"], 64)
|
||||||
|
|
||||||
|
// Container available, retrieve its node ID and its genesis json
|
||||||
|
var out []byte
|
||||||
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 /geth --exec admin.nodeInfo.id attach", network, kind)); err != nil {
|
||||||
|
return nil, ErrServiceUnreachable
|
||||||
|
}
|
||||||
|
id := bytes.Trim(bytes.TrimSpace(out), "\"")
|
||||||
|
|
||||||
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /genesis.json", network, kind)); err != nil {
|
||||||
|
return nil, ErrServiceUnreachable
|
||||||
|
}
|
||||||
|
genesis := bytes.TrimSpace(out)
|
||||||
|
|
||||||
|
keyJSON, keyPass := "", ""
|
||||||
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.json", network, kind)); err == nil {
|
||||||
|
keyJSON = string(bytes.TrimSpace(out))
|
||||||
|
}
|
||||||
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.pass", network, kind)); err == nil {
|
||||||
|
keyPass = string(bytes.TrimSpace(out))
|
||||||
|
}
|
||||||
|
// Run a sanity check to see if the devp2p is reachable
|
||||||
|
port := infos.portmap[infos.envvars["FULL_PORT"]]
|
||||||
|
if err = checkPort(client.server, port); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("%s devp2p port seems unreachable", strings.Title(kind)), "server", client.server, "port", port, "err", err)
|
||||||
|
}
|
||||||
|
// Assemble and return the useful infos
|
||||||
|
stats := &nodeInfos{
|
||||||
|
genesis: genesis,
|
||||||
|
datadir: infos.volumes["/root/.ethereum"],
|
||||||
|
portFull: infos.portmap[infos.envvars["FULL_PORT"]],
|
||||||
|
portLight: infos.portmap[infos.envvars["LIGHT_PORT"]],
|
||||||
|
peersTotal: totalPeers,
|
||||||
|
peersLight: lightPeers,
|
||||||
|
ethstats: infos.envvars["STATS_NAME"],
|
||||||
|
etherbase: infos.envvars["MINER_NAME"],
|
||||||
|
keyJSON: keyJSON,
|
||||||
|
keyPass: keyPass,
|
||||||
|
gasTarget: gasTarget,
|
||||||
|
gasPrice: gasPrice,
|
||||||
|
}
|
||||||
|
stats.enodeFull = fmt.Sprintf("enode://%s@%s:%d", id, client.address, stats.portFull)
|
||||||
|
if stats.portLight != 0 {
|
||||||
|
stats.enodeLight = fmt.Sprintf("enode://%s@%s:%d?discport=%d", id, client.address, stats.portFull, stats.portLight)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
55
cmd/puppeth/puppeth.go
Normal file
55
cmd/puppeth/puppeth.go
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// puppeth is a command to assemble and maintain private networks.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// main is just a boring entry point to set up the CLI app.
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "puppeth"
|
||||||
|
app.Usage = "assemble and maintain private Ethereum networks"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "network",
|
||||||
|
Usage: "name of the network to administer",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "loglevel",
|
||||||
|
Value: 4,
|
||||||
|
Usage: "log level to emit to the screen",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
app.Action = func(c *cli.Context) error {
|
||||||
|
// Set up the logger to print everything and the random generator
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
// Start the wizard and relinquish control
|
||||||
|
makeWizard(c.String("network")).run()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
234
cmd/puppeth/ssh.go
Normal file
234
cmd/puppeth/ssh.go
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sshClient is a small wrapper around Go's SSH client with a few utility methods
|
||||||
|
// implemented on top.
|
||||||
|
type sshClient struct {
|
||||||
|
server string // Server name or IP without port number
|
||||||
|
address string // IP address of the remote server
|
||||||
|
pubkey []byte // RSA public key to authenticate the server
|
||||||
|
client *ssh.Client
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// dial establishes an SSH connection to a remote node using the current user and
|
||||||
|
// the user's configured private RSA key. If that fails, password authentication
|
||||||
|
// is fallen back to. The caller may override the login user via user@server:port.
|
||||||
|
func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||||
|
// Figure out a label for the server and a logger
|
||||||
|
label := server
|
||||||
|
if strings.Contains(label, ":") {
|
||||||
|
label = label[:strings.Index(label, ":")]
|
||||||
|
}
|
||||||
|
login := ""
|
||||||
|
if strings.Contains(server, "@") {
|
||||||
|
login = label[:strings.Index(label, "@")]
|
||||||
|
label = label[strings.Index(label, "@")+1:]
|
||||||
|
server = server[strings.Index(server, "@")+1:]
|
||||||
|
}
|
||||||
|
logger := log.New("server", label)
|
||||||
|
logger.Debug("Attempting to establish SSH connection")
|
||||||
|
|
||||||
|
user, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if login == "" {
|
||||||
|
login = user.Username
|
||||||
|
}
|
||||||
|
// Configure the supported authentication methods (private key and password)
|
||||||
|
var auths []ssh.AuthMethod
|
||||||
|
|
||||||
|
path := filepath.Join(user.HomeDir, ".ssh", "id_rsa")
|
||||||
|
if buf, err := ioutil.ReadFile(path); err != nil {
|
||||||
|
log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
|
||||||
|
} else {
|
||||||
|
key, err := ssh.ParsePrivateKey(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("Bad SSH key, falling back to passwords", "path", path, "err", err)
|
||||||
|
} else {
|
||||||
|
auths = append(auths, ssh.PublicKeys(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auths = append(auths, ssh.PasswordCallback(func() (string, error) {
|
||||||
|
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
|
||||||
|
blob, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
return string(blob), err
|
||||||
|
}))
|
||||||
|
// Resolve the IP address of the remote server
|
||||||
|
addr, err := net.LookupHost(label)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(addr) == 0 {
|
||||||
|
return nil, errors.New("no IPs associated with domain")
|
||||||
|
}
|
||||||
|
// Try to dial in to the remote server
|
||||||
|
logger.Trace("Dialing remote SSH server", "user", login)
|
||||||
|
if !strings.Contains(server, ":") {
|
||||||
|
server += ":22"
|
||||||
|
}
|
||||||
|
keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
// If no public key is known for SSH, ask the user to confirm
|
||||||
|
if pubkey == nil {
|
||||||
|
fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote)
|
||||||
|
fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key))
|
||||||
|
fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
|
||||||
|
|
||||||
|
text, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
|
case strings.TrimSpace(text) == "yes":
|
||||||
|
pubkey = key.Marshal()
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown auth choice: %v", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If a public key exists for this SSH server, check that it matches
|
||||||
|
if bytes.Compare(pubkey, key.Marshal()) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// We have a mismatch, forbid connecting
|
||||||
|
return errors.New("ssh key mismatch, readd the machine to update")
|
||||||
|
}
|
||||||
|
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Connection established, return our utility wrapper
|
||||||
|
c := &sshClient{
|
||||||
|
server: label,
|
||||||
|
address: addr[0],
|
||||||
|
pubkey: pubkey,
|
||||||
|
client: client,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
if err := c.init(); err != nil {
|
||||||
|
client.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// init runs some initialization commands on the remote server to ensure it's
|
||||||
|
// capable of acting as puppeth target.
|
||||||
|
func (client *sshClient) init() error {
|
||||||
|
client.logger.Debug("Verifying if docker is available")
|
||||||
|
if out, err := client.Run("docker version"); err != nil {
|
||||||
|
if len(out) == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("docker configured incorrectly: %s", out)
|
||||||
|
}
|
||||||
|
client.logger.Debug("Verifying if docker-compose is available")
|
||||||
|
if out, err := client.Run("docker-compose version"); err != nil {
|
||||||
|
if len(out) == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("docker-compose configured incorrectly: %s", out)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close terminates the connection to an SSH server.
|
||||||
|
func (client *sshClient) Close() error {
|
||||||
|
return client.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes a command on the remote server and returns the combined output
|
||||||
|
// along with any error status.
|
||||||
|
func (client *sshClient) Run(cmd string) ([]byte, error) {
|
||||||
|
// Establish a single command session
|
||||||
|
session, err := client.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// Execute the command and return any output
|
||||||
|
client.logger.Trace("Running command on remote server", "cmd", cmd)
|
||||||
|
return session.CombinedOutput(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream executes a command on the remote server and streams all outputs into
|
||||||
|
// the local stdout and stderr streams.
|
||||||
|
func (client *sshClient) Stream(cmd string) error {
|
||||||
|
// Establish a single command session
|
||||||
|
session, err := client.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
session.Stdout = os.Stdout
|
||||||
|
session.Stderr = os.Stderr
|
||||||
|
|
||||||
|
// Execute the command and return any output
|
||||||
|
client.logger.Trace("Streaming command on remote server", "cmd", cmd)
|
||||||
|
return session.Run(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload copied the set of files to a remote server via SCP, creating any non-
|
||||||
|
// existing folder in te mean time.
|
||||||
|
func (client *sshClient) Upload(files map[string][]byte) ([]byte, error) {
|
||||||
|
// Establish a single command session
|
||||||
|
session, err := client.client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// Create a goroutine that streams the SCP content
|
||||||
|
go func() {
|
||||||
|
out, _ := session.StdinPipe()
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
for file, content := range files {
|
||||||
|
client.logger.Trace("Uploading file to server", "file", file, "bytes", len(content))
|
||||||
|
|
||||||
|
fmt.Fprintln(out, "D0755", 0, filepath.Dir(file)) // Ensure the folder exists
|
||||||
|
fmt.Fprintln(out, "C0644", len(content), filepath.Base(file)) // Create the actual file
|
||||||
|
out.Write(content) // Stream the data content
|
||||||
|
fmt.Fprint(out, "\x00") // Transfer end with \x00
|
||||||
|
fmt.Fprintln(out, "E") // Leave directory (simpler)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return session.CombinedOutput("/usr/bin/scp -v -tr ./")
|
||||||
|
}
|
||||||
281
cmd/puppeth/wizard.go
Normal file
281
cmd/puppeth/wizard.go
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// config contains all the configurations needed by puppeth that should be saved
|
||||||
|
// between sessions.
|
||||||
|
type config struct {
|
||||||
|
path string // File containing the configuration values
|
||||||
|
genesis *core.Genesis // Genesis block to cache for node deploys
|
||||||
|
bootFull []string // Bootnodes to always connect to by full nodes
|
||||||
|
bootLight []string // Bootnodes to always connect to by light nodes
|
||||||
|
ethstats string // Ethstats settings to cache for node deploys
|
||||||
|
|
||||||
|
Servers map[string][]byte `json:"servers,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// servers retrieves an alphabetically sorted list of servers.
|
||||||
|
func (c config) servers() []string {
|
||||||
|
servers := make([]string, 0, len(c.Servers))
|
||||||
|
for server := range c.Servers {
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
sort.Strings(servers)
|
||||||
|
|
||||||
|
return servers
|
||||||
|
}
|
||||||
|
|
||||||
|
// flush dumps the contents of config to disk.
|
||||||
|
func (c config) flush() {
|
||||||
|
os.MkdirAll(filepath.Dir(c.path), 0755)
|
||||||
|
|
||||||
|
out, _ := json.MarshalIndent(c, "", " ")
|
||||||
|
if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
|
||||||
|
log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type wizard struct {
|
||||||
|
network string // Network name to manage
|
||||||
|
conf config // Configurations from previous runs
|
||||||
|
|
||||||
|
servers map[string]*sshClient // SSH connections to servers to administer
|
||||||
|
services map[string][]string // Ethereum services known to be running on servers
|
||||||
|
|
||||||
|
in *bufio.Reader // Wrapper around stdin to allow reading user input
|
||||||
|
}
|
||||||
|
|
||||||
|
// read reads a single line from stdin, trimming if from spaces.
|
||||||
|
func (w *wizard) read() string {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readString reads a single line from stdin, trimming if from spaces, enforcing
|
||||||
|
// non-emptyness.
|
||||||
|
func (w *wizard) readString() string {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text != "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDefaultString reads a single line from stdin, trimming if from spaces. If
|
||||||
|
// an empty line is entered, the default value is returned.
|
||||||
|
func (w *wizard) readDefaultString(def string) string {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text != "" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt reads a single line from stdin, trimming if from spaces, enforcing it
|
||||||
|
// to parse into an integer.
|
||||||
|
func (w *wizard) readInt() int {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, err := strconv.Atoi(strings.TrimSpace(text))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Invalid input, expected integer", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing
|
||||||
|
// it to parse into an integer. If an empty line is entered, the default value is
|
||||||
|
// returned.
|
||||||
|
func (w *wizard) readDefaultInt(def int) int {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
val, err := strconv.Atoi(strings.TrimSpace(text))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Invalid input, expected integer", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFloat reads a single line from stdin, trimming if from spaces, enforcing it
|
||||||
|
// to parse into a float.
|
||||||
|
func (w *wizard) readFloat() float64 {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Invalid input, expected float", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing
|
||||||
|
// it to parse into a float. If an empty line is entered, the default value is returned.
|
||||||
|
func (w *wizard) readDefaultFloat(def float64) float64 {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Invalid input, expected float", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readPassword reads a single line from stdin, trimming it from the trailing new
|
||||||
|
// line and returns it. The input will not be echoed.
|
||||||
|
func (w *wizard) readPassword() string {
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
text, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read password", "err", err)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
return string(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readAddress reads a single line from stdin, trimming if from spaces and converts
|
||||||
|
// it to an Ethereum address.
|
||||||
|
func (w *wizard) readAddress() *common.Address {
|
||||||
|
for {
|
||||||
|
// Read the address from the user
|
||||||
|
fmt.Printf("> 0x")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Make sure it looks ok and return it if so
|
||||||
|
if len(text) != 40 {
|
||||||
|
log.Error("Invalid address length, please retry")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bigaddr, _ := new(big.Int).SetString(text, 16)
|
||||||
|
address := common.BigToAddress(bigaddr)
|
||||||
|
return &address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDefaultAddress reads a single line from stdin, trimming if from spaces and
|
||||||
|
// converts it to an Ethereum address. If an empty line is entered, the default
|
||||||
|
// value is returned.
|
||||||
|
func (w *wizard) readDefaultAddress(def common.Address) common.Address {
|
||||||
|
for {
|
||||||
|
// Read the address from the user
|
||||||
|
fmt.Printf("> 0x")
|
||||||
|
text, err := w.in.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
if text = strings.TrimSpace(text); text == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
// Make sure it looks ok and return it if so
|
||||||
|
if len(text) != 40 {
|
||||||
|
log.Error("Invalid address length, please retry")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bigaddr, _ := new(big.Int).SetString(text, 16)
|
||||||
|
return common.BigToAddress(bigaddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readJSON reads a raw JSON message and returns it.
|
||||||
|
func (w *wizard) readJSON() string {
|
||||||
|
var blob json.RawMessage
|
||||||
|
|
||||||
|
for {
|
||||||
|
fmt.Printf("> ")
|
||||||
|
if err := json.NewDecoder(w.in).Decode(&blob); err != nil {
|
||||||
|
log.Error("Invalid JSON, please try again", "err", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return string(blob)
|
||||||
|
}
|
||||||
|
}
|
||||||
132
cmd/puppeth/wizard_dashboard.go
Normal file
132
cmd/puppeth/wizard_dashboard.go
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deployDashboard queries the user for various input on deploying a web-service
|
||||||
|
// dashboard, after which is pushes the container.
|
||||||
|
func (w *wizard) deployDashboard() {
|
||||||
|
// Select the server to interact with
|
||||||
|
server := w.selectServer()
|
||||||
|
if server == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := w.servers[server]
|
||||||
|
|
||||||
|
// Retrieve any active dashboard configurations from the server
|
||||||
|
infos, err := checkDashboard(client, w.network)
|
||||||
|
if err != nil {
|
||||||
|
infos = &dashboardInfos{
|
||||||
|
port: 80,
|
||||||
|
host: client.server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Figure out which port to listen on
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which port should the dashboard listen on? (default = %d)\n", infos.port)
|
||||||
|
infos.port = w.readDefaultInt(infos.port)
|
||||||
|
|
||||||
|
// Figure which virtual-host to deploy the dashboard on
|
||||||
|
infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to decide on dashboard host", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Port and proxy settings retrieved, figure out which services are available
|
||||||
|
available := make(map[string][]string)
|
||||||
|
for server, services := range w.services {
|
||||||
|
for _, service := range services {
|
||||||
|
available[service] = append(available[service], server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listing := make(map[string]string)
|
||||||
|
for _, service := range []string{"ethstats", "explorer", "wallet", "faucet"} {
|
||||||
|
// Gather all the locally hosted pages of this type
|
||||||
|
var pages []string
|
||||||
|
for _, server := range available[service] {
|
||||||
|
client := w.servers[server]
|
||||||
|
if client == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If there's a service running on the machine, retrieve it's port number
|
||||||
|
var port int
|
||||||
|
switch service {
|
||||||
|
case "ethstats":
|
||||||
|
if infos, err := checkEthstats(client, w.network); err == nil {
|
||||||
|
port = infos.port
|
||||||
|
}
|
||||||
|
case "faucet":
|
||||||
|
if infos, err := checkFaucet(client, w.network); err == nil {
|
||||||
|
port = infos.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if page, err := resolve(client, w.network, service, port); err == nil && page != "" {
|
||||||
|
pages = append(pages, page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Promt the user to chose one, enter manually or simply not list this service
|
||||||
|
defLabel, defChoice := "don't list", len(pages)+2
|
||||||
|
if len(pages) > 0 {
|
||||||
|
defLabel, defChoice = pages[0], 1
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which %s service to list? (default = %s)\n", service, defLabel)
|
||||||
|
for i, page := range pages {
|
||||||
|
fmt.Printf(" %d. %s\n", i+1, page)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %d. List external %s service\n", len(pages)+1, service)
|
||||||
|
fmt.Printf(" %d. Don't list any %s service\n", len(pages)+2, service)
|
||||||
|
|
||||||
|
choice := w.readDefaultInt(defChoice)
|
||||||
|
if choice < 0 || choice > len(pages)+2 {
|
||||||
|
log.Error("Invalid listing choice, aborting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case choice <= len(pages):
|
||||||
|
listing[service] = pages[choice-1]
|
||||||
|
case choice == len(pages)+1:
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which address is the external %s service at?\n", service)
|
||||||
|
listing[service] = w.readString()
|
||||||
|
default:
|
||||||
|
// No service hosting for this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we have ethstats running, ask whether to make the secret public or not
|
||||||
|
var ethstats bool
|
||||||
|
if w.conf.ethstats != "" {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Include ethstats secret on dashboard (y/n)? (default = yes)")
|
||||||
|
ethstats = w.readDefaultString("y") == "y"
|
||||||
|
}
|
||||||
|
// Try to deploy the dashboard container on the host
|
||||||
|
if out, err := deployDashboard(client, w.network, infos.port, infos.host, listing, &w.conf, ethstats); err != nil {
|
||||||
|
log.Error("Failed to deploy dashboard container", "err", err)
|
||||||
|
if len(out) > 0 {
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// All ok, run a network scan to pick any changes up
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
79
cmd/puppeth/wizard_ethstats.go
Normal file
79
cmd/puppeth/wizard_ethstats.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deployEthstats queries the user for various input on deploying an ethstats
|
||||||
|
// monitoring server, after which it executes it.
|
||||||
|
func (w *wizard) deployEthstats() {
|
||||||
|
// Select the server to interact with
|
||||||
|
server := w.selectServer()
|
||||||
|
if server == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := w.servers[server]
|
||||||
|
|
||||||
|
// Retrieve any active ethstats configurations from the server
|
||||||
|
infos, err := checkEthstats(client, w.network)
|
||||||
|
if err != nil {
|
||||||
|
infos = ðstatsInfos{
|
||||||
|
port: 80,
|
||||||
|
host: client.server,
|
||||||
|
secret: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Figure out which port to listen on
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which port should ethstats listen on? (default = %d)\n", infos.port)
|
||||||
|
infos.port = w.readDefaultInt(infos.port)
|
||||||
|
|
||||||
|
// Figure which virtual-host to deploy ethstats on
|
||||||
|
if infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host); err != nil {
|
||||||
|
log.Error("Failed to decide on ethstats host", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Port and proxy settings retrieved, figure out the secret and boot ethstats
|
||||||
|
fmt.Println()
|
||||||
|
if infos.secret == "" {
|
||||||
|
fmt.Printf("What should be the secret password for the API? (must not be empty)\n")
|
||||||
|
infos.secret = w.readString()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("What should be the secret password for the API? (default = %s)\n", infos.secret)
|
||||||
|
infos.secret = w.readDefaultString(infos.secret)
|
||||||
|
}
|
||||||
|
// Try to deploy the ethstats server on the host
|
||||||
|
trusted := make([]string, 0, len(w.servers))
|
||||||
|
for _, client := range w.servers {
|
||||||
|
if client != nil {
|
||||||
|
trusted = append(trusted, client.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if out, err := deployEthstats(client, w.network, infos.port, infos.secret, infos.host, trusted); err != nil {
|
||||||
|
log.Error("Failed to deploy ethstats container", "err", err)
|
||||||
|
if len(out) > 0 {
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// All ok, run a network scan to pick any changes up
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
202
cmd/puppeth/wizard_faucet.go
Normal file
202
cmd/puppeth/wizard_faucet.go
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deployFaucet queries the user for various input on deploying a faucet, after
|
||||||
|
// which it executes it.
|
||||||
|
func (w *wizard) deployFaucet() {
|
||||||
|
// Select the server to interact with
|
||||||
|
server := w.selectServer()
|
||||||
|
if server == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := w.servers[server]
|
||||||
|
|
||||||
|
// Retrieve any active faucet configurations from the server
|
||||||
|
infos, err := checkFaucet(client, w.network)
|
||||||
|
if err != nil {
|
||||||
|
infos = &faucetInfos{
|
||||||
|
node: &nodeInfos{portFull: 30303, peersTotal: 25},
|
||||||
|
port: 80,
|
||||||
|
host: client.server,
|
||||||
|
amount: 1,
|
||||||
|
minutes: 1440,
|
||||||
|
tiers: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
infos.node.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ")
|
||||||
|
infos.node.network = w.conf.genesis.Config.ChainId.Int64()
|
||||||
|
|
||||||
|
// Figure out which port to listen on
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which port should the faucet listen on? (default = %d)\n", infos.port)
|
||||||
|
infos.port = w.readDefaultInt(infos.port)
|
||||||
|
|
||||||
|
// Figure which virtual-host to deploy ethstats on
|
||||||
|
if infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host); err != nil {
|
||||||
|
log.Error("Failed to decide on faucet host", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Port and proxy settings retrieved, figure out the funcing amount per perdion configurations
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("How many Ethers to release per request? (default = %d)\n", infos.amount)
|
||||||
|
infos.amount = w.readDefaultInt(infos.amount)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("How many minutes to enforce between requests? (default = %d)\n", infos.minutes)
|
||||||
|
infos.minutes = w.readDefaultInt(infos.minutes)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("How many funding tiers to feature (x2.5 amounts, x3 timeout)? (default = %d)\n", infos.tiers)
|
||||||
|
infos.tiers = w.readDefaultInt(infos.tiers)
|
||||||
|
if infos.tiers == 0 {
|
||||||
|
log.Error("At least one funding tier must be set")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Accessing GitHub gists requires API authorization, retrieve it
|
||||||
|
if infos.githubUser != "" {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Reuse previous (%s) GitHub API authorization (y/n)? (default = yes)\n", infos.githubUser)
|
||||||
|
if w.readDefaultString("y") != "y" {
|
||||||
|
infos.githubUser, infos.githubToken = "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if infos.githubUser == "" {
|
||||||
|
// No previous authorization (or new one requested)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Which GitHub user to verify Gists through?")
|
||||||
|
infos.githubUser = w.readString()
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("What is the GitHub personal access token of the user? (won't be echoed)")
|
||||||
|
infos.githubToken = w.readPassword()
|
||||||
|
|
||||||
|
// Do a sanity check query against github to ensure it's valid
|
||||||
|
req, _ := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||||
|
req.SetBasicAuth(infos.githubUser, infos.githubToken)
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to verify GitHub authentication", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
var msg struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
if err = json.NewDecoder(res.Body).Decode(&msg); err != nil {
|
||||||
|
log.Error("Failed to decode authorization response", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.Login != infos.githubUser {
|
||||||
|
log.Error("GitHub authorization failed", "user", infos.githubUser, "message", msg.Message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Accessing the reCaptcha service requires API authorizations, request it
|
||||||
|
if infos.captchaToken != "" {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Reuse previous reCaptcha API authorization (y/n)? (default = yes)")
|
||||||
|
if w.readDefaultString("y") != "y" {
|
||||||
|
infos.captchaToken, infos.captchaSecret = "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if infos.captchaToken == "" {
|
||||||
|
// No previous authorization (or old one discarded)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Enable reCaptcha protection against robots (y/n)? (default = no)")
|
||||||
|
if w.readDefaultString("n") == "y" {
|
||||||
|
// Captcha protection explicitly requested, read the site and secret keys
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("What is the reCaptcha site key to authenticate human users?\n")
|
||||||
|
infos.captchaToken = w.readString()
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("What is the reCaptcha secret key to verify authentications? (won't be echoed)\n")
|
||||||
|
infos.captchaSecret = w.readPassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Figure out where the user wants to store the persistent data
|
||||||
|
fmt.Println()
|
||||||
|
if infos.node.datadir == "" {
|
||||||
|
fmt.Printf("Where should data be stored on the remote machine?\n")
|
||||||
|
infos.node.datadir = w.readString()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.node.datadir)
|
||||||
|
infos.node.datadir = w.readDefaultString(infos.node.datadir)
|
||||||
|
}
|
||||||
|
// Figure out which port to listen on
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which TCP/UDP port should the light client listen on? (default = %d)\n", infos.node.portFull)
|
||||||
|
infos.node.portFull = w.readDefaultInt(infos.node.portFull)
|
||||||
|
|
||||||
|
// Set a proper name to report on the stats page
|
||||||
|
fmt.Println()
|
||||||
|
if infos.node.ethstats == "" {
|
||||||
|
fmt.Printf("What should the node be called on the stats page?\n")
|
||||||
|
infos.node.ethstats = w.readString() + ":" + w.conf.ethstats
|
||||||
|
} else {
|
||||||
|
fmt.Printf("What should the node be called on the stats page? (default = %s)\n", infos.node.ethstats)
|
||||||
|
infos.node.ethstats = w.readDefaultString(infos.node.ethstats) + ":" + w.conf.ethstats
|
||||||
|
}
|
||||||
|
// Load up the credential needed to release funds
|
||||||
|
if infos.node.keyJSON != "" {
|
||||||
|
if key, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
|
||||||
|
infos.node.keyJSON, infos.node.keyPass = "", ""
|
||||||
|
} else {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Reuse previous (%s) funding account (y/n)? (default = yes)\n", key.Address.Hex())
|
||||||
|
if w.readDefaultString("y") != "y" {
|
||||||
|
infos.node.keyJSON, infos.node.keyPass = "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if infos.node.keyJSON == "" {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Please paste the faucet's funding account key JSON:")
|
||||||
|
infos.node.keyJSON = w.readJSON()
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("What's the unlock password for the account? (won't be echoed)")
|
||||||
|
infos.node.keyPass = w.readPassword()
|
||||||
|
|
||||||
|
if _, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
|
||||||
|
log.Error("Failed to decrypt key with given passphrase")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to deploy the faucet server on the host
|
||||||
|
if out, err := deployFaucet(client, w.network, w.conf.bootLight, infos); err != nil {
|
||||||
|
log.Error("Failed to deploy faucet container", "err", err)
|
||||||
|
if len(out) > 0 {
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// All ok, run a network scan to pick any changes up
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
136
cmd/puppeth/wizard_genesis.go
Normal file
136
cmd/puppeth/wizard_genesis.go
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeGenesis creates a new genesis struct based on some user input.
|
||||||
|
func (w *wizard) makeGenesis() {
|
||||||
|
// Construct a default genesis block
|
||||||
|
genesis := &core.Genesis{
|
||||||
|
Timestamp: uint64(time.Now().Unix()),
|
||||||
|
GasLimit: 4700000,
|
||||||
|
Difficulty: big.NewInt(1048576),
|
||||||
|
Alloc: make(core.GenesisAlloc),
|
||||||
|
Config: ¶ms.ChainConfig{
|
||||||
|
HomesteadBlock: big.NewInt(1),
|
||||||
|
EIP150Block: big.NewInt(2),
|
||||||
|
EIP155Block: big.NewInt(3),
|
||||||
|
EIP158Block: big.NewInt(3),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// Figure out which consensus engine to choose
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Which consensus engine to use? (default = clique)")
|
||||||
|
fmt.Println(" 1. Ethash - proof-of-work")
|
||||||
|
fmt.Println(" 2. Clique - proof-of-authority")
|
||||||
|
|
||||||
|
choice := w.read()
|
||||||
|
switch {
|
||||||
|
case choice == "1":
|
||||||
|
// In case of ethash, we're pretty much done
|
||||||
|
genesis.Config.Ethash = new(params.EthashConfig)
|
||||||
|
genesis.ExtraData = make([]byte, 32)
|
||||||
|
|
||||||
|
case choice == "" || choice == "2":
|
||||||
|
// In the case of clique, configure the consensus parameters
|
||||||
|
genesis.Difficulty = big.NewInt(1)
|
||||||
|
genesis.Config.Clique = ¶ms.CliqueConfig{
|
||||||
|
Period: 15,
|
||||||
|
Epoch: 30000,
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("How many seconds should blocks take? (default = 15)")
|
||||||
|
genesis.Config.Clique.Period = uint64(w.readDefaultInt(15))
|
||||||
|
|
||||||
|
// We also need the initial list of signers
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Which accounts are allowed to seal? (mandatory at least one)")
|
||||||
|
|
||||||
|
var signers []common.Address
|
||||||
|
for {
|
||||||
|
if address := w.readAddress(); address != nil {
|
||||||
|
signers = append(signers, *address)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(signers) > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort the signers and embed into the extra-data section
|
||||||
|
for i := 0; i < len(signers); i++ {
|
||||||
|
for j := i + 1; j < len(signers); j++ {
|
||||||
|
if bytes.Compare(signers[i][:], signers[j][:]) > 0 {
|
||||||
|
signers[i], signers[j] = signers[j], signers[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65)
|
||||||
|
for i, signer := range signers {
|
||||||
|
copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Crit("Invalid consensus engine choice", "choice", choice)
|
||||||
|
}
|
||||||
|
// Consensus all set, just ask for initial funds and go
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Which accounts should be pre-funded? (advisable at least one)")
|
||||||
|
for {
|
||||||
|
// Read the address of the account to fund
|
||||||
|
if address := w.readAddress(); address != nil {
|
||||||
|
genesis.Alloc[*address] = core.GenesisAccount{
|
||||||
|
Balance: new(big.Int).Lsh(big.NewInt(1), 256-7), // 2^256 / 128 (allow many pre-funds without balance overflows)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Add a batch of precompile balances to avoid them getting deleted
|
||||||
|
for i := int64(0); i < 256; i++ {
|
||||||
|
genesis.Alloc[common.BigToAddress(big.NewInt(i))] = core.GenesisAccount{Balance: big.NewInt(1)}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Query the user for some custom extras
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)")
|
||||||
|
genesis.Config.ChainId = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536))))
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Anything fun to embed into the genesis block? (max 32 bytes)")
|
||||||
|
|
||||||
|
extra := w.read()
|
||||||
|
if len(extra) > 32 {
|
||||||
|
extra = extra[:32]
|
||||||
|
}
|
||||||
|
genesis.ExtraData = append([]byte(extra), genesis.ExtraData[len(extra):]...)
|
||||||
|
|
||||||
|
// All done, store the genesis and flush to disk
|
||||||
|
w.conf.genesis = genesis
|
||||||
|
}
|
||||||
156
cmd/puppeth/wizard_intro.go
Normal file
156
cmd/puppeth/wizard_intro.go
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// makeWizard creates and returns a new puppeth wizard.
|
||||||
|
func makeWizard(network string) *wizard {
|
||||||
|
return &wizard{
|
||||||
|
network: network,
|
||||||
|
conf: config{
|
||||||
|
Servers: make(map[string][]byte),
|
||||||
|
},
|
||||||
|
servers: make(map[string]*sshClient),
|
||||||
|
services: make(map[string][]string),
|
||||||
|
in: bufio.NewReader(os.Stdin),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run displays some useful infos to the user, starting on the journey of
|
||||||
|
// setting up a new or managing an existing Ethereum private network.
|
||||||
|
func (w *wizard) run() {
|
||||||
|
fmt.Println("+-----------------------------------------------------------+")
|
||||||
|
fmt.Println("| Welcome to puppeth, your Ethereum private network manager |")
|
||||||
|
fmt.Println("| |")
|
||||||
|
fmt.Println("| This tool lets you create a new Ethereum network down to |")
|
||||||
|
fmt.Println("| the genesis block, bootnodes, miners and ethstats servers |")
|
||||||
|
fmt.Println("| without the hassle that it would normally entail. |")
|
||||||
|
fmt.Println("| |")
|
||||||
|
fmt.Println("| Puppeth uses SSH to dial in to remote servers, and builds |")
|
||||||
|
fmt.Println("| its network components out of Docker containers using the |")
|
||||||
|
fmt.Println("| docker-compose toolset. |")
|
||||||
|
fmt.Println("+-----------------------------------------------------------+")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Make sure we have a good network name to work with fmt.Println()
|
||||||
|
if w.network == "" {
|
||||||
|
fmt.Println("Please specify a network name to administer (no spaces, please)")
|
||||||
|
for {
|
||||||
|
w.network = w.readString()
|
||||||
|
if !strings.Contains(w.network, " ") {
|
||||||
|
fmt.Printf("Sweet, you can set this via --network=%s next time!\n\n", w.network)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Error("I also like to live dangerously, still no spaces")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("Administering Ethereum network", "name", w.network)
|
||||||
|
|
||||||
|
// Load initial configurations and connect to all live servers
|
||||||
|
w.conf.path = filepath.Join(os.Getenv("HOME"), ".puppeth", w.network)
|
||||||
|
|
||||||
|
blob, err := ioutil.ReadFile(w.conf.path)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("No previous configurations found", "path", w.conf.path)
|
||||||
|
} else if err := json.Unmarshal(blob, &w.conf); err != nil {
|
||||||
|
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
|
||||||
|
} else {
|
||||||
|
for server, pubkey := range w.conf.Servers {
|
||||||
|
log.Info("Dialing previously configured server", "server", server)
|
||||||
|
client, err := dial(server, pubkey)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Previous server unreachable", "server", server, "err", err)
|
||||||
|
}
|
||||||
|
w.servers[server] = client
|
||||||
|
}
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
|
// Basics done, loop ad infinitum about what to do
|
||||||
|
for {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("What would you like to do? (default = stats)")
|
||||||
|
fmt.Println(" 1. Show network stats")
|
||||||
|
if w.conf.genesis == nil {
|
||||||
|
fmt.Println(" 2. Configure new genesis")
|
||||||
|
} else {
|
||||||
|
fmt.Println(" 2. Save existing genesis")
|
||||||
|
}
|
||||||
|
if len(w.servers) == 0 {
|
||||||
|
fmt.Println(" 3. Track new remote server")
|
||||||
|
} else {
|
||||||
|
fmt.Println(" 3. Manage tracked machines")
|
||||||
|
}
|
||||||
|
if len(w.services) == 0 {
|
||||||
|
fmt.Println(" 4. Deploy network components")
|
||||||
|
} else {
|
||||||
|
fmt.Println(" 4. Manage network components")
|
||||||
|
}
|
||||||
|
//fmt.Println(" 5. ProTips for common usecases")
|
||||||
|
|
||||||
|
choice := w.read()
|
||||||
|
switch {
|
||||||
|
case choice == "" || choice == "1":
|
||||||
|
w.networkStats(false)
|
||||||
|
|
||||||
|
case choice == "2":
|
||||||
|
// If we don't have a genesis, make one
|
||||||
|
if w.conf.genesis == nil {
|
||||||
|
w.makeGenesis()
|
||||||
|
} else {
|
||||||
|
// Otherwise just save whatever we currently have
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which file to save the genesis into? (default = %s.json)\n", w.network)
|
||||||
|
out, _ := json.MarshalIndent(w.conf.genesis, "", " ")
|
||||||
|
if err := ioutil.WriteFile(w.readDefaultString(fmt.Sprintf("%s.json", w.network)), out, 0644); err != nil {
|
||||||
|
log.Error("Failed to save genesis file", "err", err)
|
||||||
|
}
|
||||||
|
log.Info("Exported existing genesis block")
|
||||||
|
}
|
||||||
|
case choice == "3":
|
||||||
|
if len(w.servers) == 0 {
|
||||||
|
if w.makeServer() != "" {
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.manageServers()
|
||||||
|
}
|
||||||
|
case choice == "4":
|
||||||
|
if len(w.services) == 0 {
|
||||||
|
w.deployComponent()
|
||||||
|
} else {
|
||||||
|
w.manageComponents()
|
||||||
|
}
|
||||||
|
|
||||||
|
case choice == "5":
|
||||||
|
w.networkStats(true)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Error("That's not something I can do")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
235
cmd/puppeth/wizard_netstats.go
Normal file
235
cmd/puppeth/wizard_netstats.go
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/olekukonko/tablewriter"
|
||||||
|
)
|
||||||
|
|
||||||
|
// networkStats verifies the status of network components and generates a protip
|
||||||
|
// configuration set to give users hints on how to do various tasks.
|
||||||
|
func (w *wizard) networkStats(tips bool) {
|
||||||
|
if len(w.servers) == 0 {
|
||||||
|
log.Error("No remote machines to gather stats from")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protips := new(protips)
|
||||||
|
|
||||||
|
// Iterate over all the specified hosts and check their status
|
||||||
|
stats := tablewriter.NewWriter(os.Stdout)
|
||||||
|
stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"})
|
||||||
|
stats.SetColWidth(100)
|
||||||
|
|
||||||
|
for server, pubkey := range w.conf.Servers {
|
||||||
|
client := w.servers[server]
|
||||||
|
logger := log.New("server", server)
|
||||||
|
logger.Info("Starting remote server health-check")
|
||||||
|
|
||||||
|
// If the server is not connected, try to connect again
|
||||||
|
if client == nil {
|
||||||
|
conn, err := dial(server, pubkey)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to establish remote connection", "err", err)
|
||||||
|
stats.Append([]string{server, "", err.Error(), "", ""})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client = conn
|
||||||
|
}
|
||||||
|
// Client connected one way or another, run health-checks
|
||||||
|
services := make(map[string]string)
|
||||||
|
logger.Debug("Checking for nginx availability")
|
||||||
|
if infos, err := checkNginx(client, w.network); err != nil {
|
||||||
|
if err != ErrServiceUnknown {
|
||||||
|
services["nginx"] = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
services["nginx"] = infos.String()
|
||||||
|
}
|
||||||
|
logger.Debug("Checking for ethstats availability")
|
||||||
|
if infos, err := checkEthstats(client, w.network); err != nil {
|
||||||
|
if err != ErrServiceUnknown {
|
||||||
|
services["ethstats"] = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
services["ethstats"] = infos.String()
|
||||||
|
protips.ethstats = infos.config
|
||||||
|
}
|
||||||
|
logger.Debug("Checking for bootnode availability")
|
||||||
|
if infos, err := checkNode(client, w.network, true); err != nil {
|
||||||
|
if err != ErrServiceUnknown {
|
||||||
|
services["bootnode"] = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
services["bootnode"] = infos.String()
|
||||||
|
|
||||||
|
protips.genesis = string(infos.genesis)
|
||||||
|
protips.bootFull = append(protips.bootFull, infos.enodeFull)
|
||||||
|
if infos.enodeLight != "" {
|
||||||
|
protips.bootLight = append(protips.bootLight, infos.enodeLight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.Debug("Checking for sealnode availability")
|
||||||
|
if infos, err := checkNode(client, w.network, false); err != nil {
|
||||||
|
if err != ErrServiceUnknown {
|
||||||
|
services["sealnode"] = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
services["sealnode"] = infos.String()
|
||||||
|
protips.genesis = string(infos.genesis)
|
||||||
|
}
|
||||||
|
logger.Debug("Checking for faucet availability")
|
||||||
|
if infos, err := checkFaucet(client, w.network); err != nil {
|
||||||
|
if err != ErrServiceUnknown {
|
||||||
|
services["faucet"] = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
services["faucet"] = infos.String()
|
||||||
|
}
|
||||||
|
logger.Debug("Checking for dashboard availability")
|
||||||
|
if infos, err := checkDashboard(client, w.network); err != nil {
|
||||||
|
if err != ErrServiceUnknown {
|
||||||
|
services["dashboard"] = err.Error()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
services["dashboard"] = infos.String()
|
||||||
|
}
|
||||||
|
// All status checks complete, report and check next server
|
||||||
|
delete(w.services, server)
|
||||||
|
for service := range services {
|
||||||
|
w.services[server] = append(w.services[server], service)
|
||||||
|
}
|
||||||
|
server, address := client.server, client.address
|
||||||
|
for service, status := range services {
|
||||||
|
stats.Append([]string{server, address, "online", service, status})
|
||||||
|
server, address = "", ""
|
||||||
|
}
|
||||||
|
if len(services) == 0 {
|
||||||
|
stats.Append([]string{server, address, "online", "", ""})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If a genesis block was found, load it into our configs
|
||||||
|
if protips.genesis != "" {
|
||||||
|
genesis := new(core.Genesis)
|
||||||
|
if err := json.Unmarshal([]byte(protips.genesis), genesis); err != nil {
|
||||||
|
log.Error("Failed to parse remote genesis", "err", err)
|
||||||
|
} else {
|
||||||
|
w.conf.genesis = genesis
|
||||||
|
protips.network = genesis.Config.ChainId.Int64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if protips.ethstats != "" {
|
||||||
|
w.conf.ethstats = protips.ethstats
|
||||||
|
}
|
||||||
|
w.conf.bootFull = protips.bootFull
|
||||||
|
w.conf.bootLight = protips.bootLight
|
||||||
|
|
||||||
|
// Print any collected stats and return
|
||||||
|
if !tips {
|
||||||
|
stats.Render()
|
||||||
|
} else {
|
||||||
|
protips.print(w.network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protips contains a collection of network infos to report pro-tips
|
||||||
|
// based on.
|
||||||
|
type protips struct {
|
||||||
|
genesis string
|
||||||
|
network int64
|
||||||
|
bootFull []string
|
||||||
|
bootLight []string
|
||||||
|
ethstats string
|
||||||
|
}
|
||||||
|
|
||||||
|
// print analyzes the network information available and prints a collection of
|
||||||
|
// pro tips for the user's consideration.
|
||||||
|
func (p *protips) print(network string) {
|
||||||
|
// If a known genesis block is available, display it and prepend an init command
|
||||||
|
fullinit, lightinit := "", ""
|
||||||
|
if p.genesis != "" {
|
||||||
|
fullinit = fmt.Sprintf("geth --datadir=$HOME/.%s init %s.json && ", network, network)
|
||||||
|
lightinit = fmt.Sprintf("geth --datadir=$HOME/.%s --light init %s.json && ", network, network)
|
||||||
|
}
|
||||||
|
// If an ethstats server is available, add the ethstats flag
|
||||||
|
statsflag := ""
|
||||||
|
if p.ethstats != "" {
|
||||||
|
if strings.Contains(p.ethstats, " ") {
|
||||||
|
statsflag = fmt.Sprintf(` --ethstats="yournode:%s"`, p.ethstats)
|
||||||
|
} else {
|
||||||
|
statsflag = fmt.Sprintf(` --ethstats=yournode:%s`, p.ethstats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If bootnodes have been specified, add the bootnode flag
|
||||||
|
bootflagFull := ""
|
||||||
|
if len(p.bootFull) > 0 {
|
||||||
|
bootflagFull = fmt.Sprintf(` --bootnodes %s`, strings.Join(p.bootFull, ","))
|
||||||
|
}
|
||||||
|
bootflagLight := ""
|
||||||
|
if len(p.bootLight) > 0 {
|
||||||
|
bootflagLight = fmt.Sprintf(` --bootnodes %s`, strings.Join(p.bootLight, ","))
|
||||||
|
}
|
||||||
|
// Assemble all the known pro-tips
|
||||||
|
var tasks, tips []string
|
||||||
|
|
||||||
|
tasks = append(tasks, "Run an archive node with historical data")
|
||||||
|
tips = append(tips, fmt.Sprintf("%sgeth --networkid=%d --datadir=$HOME/.%s --cache=1024%s%s", fullinit, p.network, network, statsflag, bootflagFull))
|
||||||
|
|
||||||
|
tasks = append(tasks, "Run a full node with recent data only")
|
||||||
|
tips = append(tips, fmt.Sprintf("%sgeth --networkid=%d --datadir=$HOME/.%s --cache=512 --fast%s%s", fullinit, p.network, network, statsflag, bootflagFull))
|
||||||
|
|
||||||
|
tasks = append(tasks, "Run a light node with on demand retrievals")
|
||||||
|
tips = append(tips, fmt.Sprintf("%sgeth --networkid=%d --datadir=$HOME/.%s --light%s%s", lightinit, p.network, network, statsflag, bootflagLight))
|
||||||
|
|
||||||
|
tasks = append(tasks, "Run an embedded node with constrained memory")
|
||||||
|
tips = append(tips, fmt.Sprintf("%sgeth --networkid=%d --datadir=$HOME/.%s --cache=32 --light%s%s", lightinit, p.network, network, statsflag, bootflagLight))
|
||||||
|
|
||||||
|
// If the tips are short, display in a table
|
||||||
|
short := true
|
||||||
|
for _, tip := range tips {
|
||||||
|
if len(tip) > 100 {
|
||||||
|
short = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
if short {
|
||||||
|
howto := tablewriter.NewWriter(os.Stdout)
|
||||||
|
howto.SetHeader([]string{"Fun tasks for you", "Tips on how to"})
|
||||||
|
howto.SetColWidth(100)
|
||||||
|
|
||||||
|
for i := 0; i < len(tasks); i++ {
|
||||||
|
howto.Append([]string{tasks[i], tips[i]})
|
||||||
|
}
|
||||||
|
howto.Render()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Meh, tips got ugly, split into many lines
|
||||||
|
for i := 0; i < len(tasks); i++ {
|
||||||
|
fmt.Println(tasks[i])
|
||||||
|
fmt.Println(strings.Repeat("-", len(tasks[i])))
|
||||||
|
fmt.Println(tips[i])
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
198
cmd/puppeth/wizard_network.go
Normal file
198
cmd/puppeth/wizard_network.go
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// manageServers displays a list of servers the user can disconnect from, and an
|
||||||
|
// option to connect to new servers.
|
||||||
|
func (w *wizard) manageServers() {
|
||||||
|
// List all the servers we can disconnect, along with an entry to connect a new one
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
servers := w.conf.servers()
|
||||||
|
for i, server := range servers {
|
||||||
|
fmt.Printf(" %d. Disconnect %s\n", i+1, server)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
|
||||||
|
|
||||||
|
choice := w.readInt()
|
||||||
|
if choice < 0 || choice > len(w.conf.Servers)+1 {
|
||||||
|
log.Error("Invalid server choice, aborting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the user selected an existing server, drop it
|
||||||
|
if choice <= len(w.conf.Servers) {
|
||||||
|
server := servers[choice-1]
|
||||||
|
client := w.servers[server]
|
||||||
|
|
||||||
|
delete(w.servers, server)
|
||||||
|
if client != nil {
|
||||||
|
client.Close()
|
||||||
|
}
|
||||||
|
delete(w.conf.Servers, server)
|
||||||
|
w.conf.flush()
|
||||||
|
|
||||||
|
log.Info("Disconnected existing server", "server", server)
|
||||||
|
w.networkStats(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the user requested connecting a new server, do it
|
||||||
|
if w.makeServer() != "" {
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeServer reads a single line from stdin and interprets it as a hostname to
|
||||||
|
// connect to. It tries to establish a new SSH session and also executing some
|
||||||
|
// baseline validations.
|
||||||
|
//
|
||||||
|
// If connection succeeds, the server is added to the wizards configs!
|
||||||
|
func (w *wizard) makeServer() string {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Please enter remote server's address:")
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Read and fial the server to ensure docker is present
|
||||||
|
input := w.readString()
|
||||||
|
|
||||||
|
client, err := dial(input, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Server not ready for puppeth", "err", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// All checks passed, start tracking the server
|
||||||
|
w.servers[input] = client
|
||||||
|
w.conf.Servers[input] = client.pubkey
|
||||||
|
w.conf.flush()
|
||||||
|
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectServer lists the user all the currnetly known servers to choose from,
|
||||||
|
// also granting the option to add a new one.
|
||||||
|
func (w *wizard) selectServer() string {
|
||||||
|
// List the available server to the user and wait for a choice
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Which server do you want to interact with?")
|
||||||
|
|
||||||
|
servers := w.conf.servers()
|
||||||
|
for i, server := range servers {
|
||||||
|
fmt.Printf(" %d. %s\n", i+1, server)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
|
||||||
|
|
||||||
|
choice := w.readInt()
|
||||||
|
if choice < 0 || choice > len(w.conf.Servers)+1 {
|
||||||
|
log.Error("Invalid server choice, aborting")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// If the user requested connecting to a new server, go for it
|
||||||
|
if choice <= len(w.conf.Servers) {
|
||||||
|
return servers[choice-1]
|
||||||
|
}
|
||||||
|
return w.makeServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// manageComponents displays a list of network components the user can tear down
|
||||||
|
// and an option
|
||||||
|
func (w *wizard) manageComponents() {
|
||||||
|
// List all the componens we can tear down, along with an entry to deploy a new one
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
var serviceHosts, serviceNames []string
|
||||||
|
for server, services := range w.services {
|
||||||
|
for _, service := range services {
|
||||||
|
serviceHosts = append(serviceHosts, server)
|
||||||
|
serviceNames = append(serviceNames, service)
|
||||||
|
|
||||||
|
fmt.Printf(" %d. Tear down %s on %s\n", len(serviceHosts), strings.Title(service), server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf(" %d. Deploy new network component\n", len(serviceHosts)+1)
|
||||||
|
|
||||||
|
choice := w.readInt()
|
||||||
|
if choice < 0 || choice > len(serviceHosts)+1 {
|
||||||
|
log.Error("Invalid component choice, aborting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the user selected an existing service, destroy it
|
||||||
|
if choice <= len(serviceHosts) {
|
||||||
|
// Figure out the service to destroy and execute it
|
||||||
|
service := serviceNames[choice-1]
|
||||||
|
server := serviceHosts[choice-1]
|
||||||
|
client := w.servers[server]
|
||||||
|
|
||||||
|
if out, err := tearDown(client, w.network, service, true); err != nil {
|
||||||
|
log.Error("Failed to tear down component", "err", err)
|
||||||
|
if len(out) > 0 {
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Clean up any references to it from out state
|
||||||
|
services := w.services[server]
|
||||||
|
for i, name := range services {
|
||||||
|
if name == service {
|
||||||
|
w.services[server] = append(services[:i], services[i+1:]...)
|
||||||
|
if len(w.services[server]) == 0 {
|
||||||
|
delete(w.services, server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Info("Torn down existing component", "server", server, "service", service)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the user requested deploying a new component, do it
|
||||||
|
w.deployComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// deployComponent displays a list of network components the user can deploy and
|
||||||
|
// guides through the process.
|
||||||
|
func (w *wizard) deployComponent() {
|
||||||
|
// Print all the things we can deploy and wait or user choice
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("What would you like to deploy? (recommended order)")
|
||||||
|
fmt.Println(" 1. Ethstats - Network monitoring tool")
|
||||||
|
fmt.Println(" 2. Bootnode - Entry point of the network")
|
||||||
|
fmt.Println(" 3. Sealer - Full node minting new blocks")
|
||||||
|
fmt.Println(" 4. Wallet - Browser wallet for quick sends (todo)")
|
||||||
|
fmt.Println(" 5. Faucet - Crypto faucet to give away funds")
|
||||||
|
fmt.Println(" 6. Dashboard - Website listing above web-services")
|
||||||
|
|
||||||
|
switch w.read() {
|
||||||
|
case "1":
|
||||||
|
w.deployEthstats()
|
||||||
|
case "2":
|
||||||
|
w.deployNode(true)
|
||||||
|
case "3":
|
||||||
|
w.deployNode(false)
|
||||||
|
case "4":
|
||||||
|
case "5":
|
||||||
|
w.deployFaucet()
|
||||||
|
case "6":
|
||||||
|
w.deployDashboard()
|
||||||
|
default:
|
||||||
|
log.Error("That's not something I can do")
|
||||||
|
}
|
||||||
|
}
|
||||||
58
cmd/puppeth/wizard_nginx.go
Normal file
58
cmd/puppeth/wizard_nginx.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensureVirtualHost checks whether a reverse-proxy is running on the specified
|
||||||
|
// host machine, and if yes requests a virtual host from the user to host a
|
||||||
|
// specific web service on. If no proxy exists, the method will offer to deploy
|
||||||
|
// one.
|
||||||
|
//
|
||||||
|
// If the user elects not to use a reverse proxy, an empty hostname is returned!
|
||||||
|
func (w *wizard) ensureVirtualHost(client *sshClient, port int, def string) (string, error) {
|
||||||
|
if proxy, _ := checkNginx(client, w.network); proxy != nil {
|
||||||
|
// Reverse proxy is running, if ports match, we need a virtual host
|
||||||
|
if proxy.port == port {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Shared port, which domain to assign? (default = %s)\n", def)
|
||||||
|
return w.readDefaultString(def), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reverse proxy is not running, offer to deploy a new one
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Allow sharing the port with other services (y/n)? (default = yes)")
|
||||||
|
if w.readDefaultString("y") == "y" {
|
||||||
|
if out, err := deployNginx(client, w.network, port); err != nil {
|
||||||
|
log.Error("Failed to deploy reverse-proxy", "err", err)
|
||||||
|
if len(out) > 0 {
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Reverse proxy deployed, ask again for the virtual-host
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Proxy deployed, which domain to assign? (default = %s)\n", def)
|
||||||
|
return w.readDefaultString(def), nil
|
||||||
|
}
|
||||||
|
// Reverse proxy not requested, deploy as a standalone service
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
160
cmd/puppeth/wizard_node.go
Normal file
160
cmd/puppeth/wizard_node.go
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deployNode creates a new node configuration based on some user input.
|
||||||
|
func (w *wizard) deployNode(boot bool) {
|
||||||
|
// Do some sanity check before the user wastes time on input
|
||||||
|
if w.conf.genesis == nil {
|
||||||
|
log.Error("No genesis block configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if w.conf.ethstats == "" {
|
||||||
|
log.Error("No ethstats server configured")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Select the server to interact with
|
||||||
|
server := w.selectServer()
|
||||||
|
if server == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := w.servers[server]
|
||||||
|
|
||||||
|
// Retrieve any active ethstats configurations from the server
|
||||||
|
infos, err := checkNode(client, w.network, boot)
|
||||||
|
if err != nil {
|
||||||
|
if boot {
|
||||||
|
infos = &nodeInfos{portFull: 30303, peersTotal: 512, peersLight: 256}
|
||||||
|
} else {
|
||||||
|
infos = &nodeInfos{portFull: 30303, peersTotal: 50, peersLight: 0, gasTarget: 4.7, gasPrice: 18}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
infos.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ")
|
||||||
|
infos.network = w.conf.genesis.Config.ChainId.Int64()
|
||||||
|
|
||||||
|
// Figure out where the user wants to store the persistent data
|
||||||
|
fmt.Println()
|
||||||
|
if infos.datadir == "" {
|
||||||
|
fmt.Printf("Where should data be stored on the remote machine?\n")
|
||||||
|
infos.datadir = w.readString()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.datadir)
|
||||||
|
infos.datadir = w.readDefaultString(infos.datadir)
|
||||||
|
}
|
||||||
|
// Figure out which port to listen on
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Which TCP/UDP port to listen on? (default = %d)\n", infos.portFull)
|
||||||
|
infos.portFull = w.readDefaultInt(infos.portFull)
|
||||||
|
|
||||||
|
// Figure out how many peers to allow (different based on node type)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("How many peers to allow connecting? (default = %d)\n", infos.peersTotal)
|
||||||
|
infos.peersTotal = w.readDefaultInt(infos.peersTotal)
|
||||||
|
|
||||||
|
// Figure out how many light peers to allow (different based on node type)
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("How many light peers to allow connecting? (default = %d)\n", infos.peersLight)
|
||||||
|
infos.peersLight = w.readDefaultInt(infos.peersLight)
|
||||||
|
|
||||||
|
// Set a proper name to report on the stats page
|
||||||
|
fmt.Println()
|
||||||
|
if infos.ethstats == "" {
|
||||||
|
fmt.Printf("What should the node be called on the stats page?\n")
|
||||||
|
infos.ethstats = w.readString() + ":" + w.conf.ethstats
|
||||||
|
} else {
|
||||||
|
fmt.Printf("What should the node be called on the stats page? (default = %s)\n", infos.ethstats)
|
||||||
|
infos.ethstats = w.readDefaultString(infos.ethstats) + ":" + w.conf.ethstats
|
||||||
|
}
|
||||||
|
// If the node is a miner/signer, load up needed credentials
|
||||||
|
if !boot {
|
||||||
|
if w.conf.genesis.Config.Ethash != nil {
|
||||||
|
// Ethash based miners only need an etherbase to mine against
|
||||||
|
fmt.Println()
|
||||||
|
if infos.etherbase == "" {
|
||||||
|
fmt.Printf("What address should the miner user?\n")
|
||||||
|
for {
|
||||||
|
if address := w.readAddress(); address != nil {
|
||||||
|
infos.etherbase = address.Hex()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("What address should the miner user? (default = %s)\n", infos.etherbase)
|
||||||
|
infos.etherbase = w.readDefaultAddress(common.HexToAddress(infos.etherbase)).Hex()
|
||||||
|
}
|
||||||
|
} else if w.conf.genesis.Config.Clique != nil {
|
||||||
|
// If a previous signer was already set, offer to reuse it
|
||||||
|
if infos.keyJSON != "" {
|
||||||
|
if key, err := keystore.DecryptKey([]byte(infos.keyJSON), infos.keyPass); err != nil {
|
||||||
|
infos.keyJSON, infos.keyPass = "", ""
|
||||||
|
} else {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("Reuse previous (%s) signing account (y/n)? (default = yes)\n", key.Address.Hex())
|
||||||
|
if w.readDefaultString("y") != "y" {
|
||||||
|
infos.keyJSON, infos.keyPass = "", ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clique based signers need a keyfile and unlock password, ask if unavailable
|
||||||
|
if infos.keyJSON == "" {
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Please paste the signer's key JSON:")
|
||||||
|
infos.keyJSON = w.readJSON()
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("What's the unlock password for the account? (won't be echoed)")
|
||||||
|
infos.keyPass = w.readPassword()
|
||||||
|
|
||||||
|
if _, err := keystore.DecryptKey([]byte(infos.keyJSON), infos.keyPass); err != nil {
|
||||||
|
log.Error("Failed to decrypt key with given passphrase")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Establish the gas dynamics to be enforced by the signer
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("What gas limit should empty blocks target (MGas)? (default = %0.3f)\n", infos.gasTarget)
|
||||||
|
infos.gasTarget = w.readDefaultFloat(infos.gasTarget)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("What gas price should the signer require (GWei)? (default = %0.3f)\n", infos.gasPrice)
|
||||||
|
infos.gasPrice = w.readDefaultFloat(infos.gasPrice)
|
||||||
|
}
|
||||||
|
// Try to deploy the full node on the host
|
||||||
|
if out, err := deployNode(client, w.network, w.conf.bootFull, w.conf.bootLight, infos); err != nil {
|
||||||
|
log.Error("Failed to deploy Ethereum node container", "err", err)
|
||||||
|
if len(out) > 0 {
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// All ok, run a network scan to pick any changes up
|
||||||
|
log.Info("Waiting for node to finish booting")
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
w.networkStats(false)
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ import (
|
||||||
var (
|
var (
|
||||||
hexMode = flag.String("hex", "", "dump given hex data")
|
hexMode = flag.String("hex", "", "dump given hex data")
|
||||||
noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably")
|
noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably")
|
||||||
|
single = flag.Bool("single", false, "print only the first element, discard the rest")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
@ -82,6 +83,9 @@ func main() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
if *single {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of go-ethereum.
|
||||||
//
|
//
|
||||||
// The go-ethereum library 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 Lesser General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
// GNU Lesser General Public License for more details.
|
// GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of go-ethereum.
|
// This file is part of go-ethereum.
|
||||||
//
|
//
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,25 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/console"
|
"github.com/ethereum/go-ethereum/console"
|
||||||
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
|
|
@ -39,19 +43,17 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const clientIdentifier = "swarm"
|
||||||
clientIdentifier = "swarm"
|
|
||||||
versionString = "0.2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||||
app = utils.NewApp(gitCommit, "Ethereum Swarm")
|
|
||||||
testbetBootNodes = []string{
|
testbetBootNodes = []string{
|
||||||
"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
|
"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
|
||||||
"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
|
"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
|
||||||
|
|
@ -70,6 +72,10 @@ var (
|
||||||
Name: "bzzaccount",
|
Name: "bzzaccount",
|
||||||
Usage: "Swarm account key file",
|
Usage: "Swarm account key file",
|
||||||
}
|
}
|
||||||
|
SwarmListenAddrFlag = cli.StringFlag{
|
||||||
|
Name: "httpaddr",
|
||||||
|
Usage: "Swarm HTTP API listening interface",
|
||||||
|
}
|
||||||
SwarmPortFlag = cli.StringFlag{
|
SwarmPortFlag = cli.StringFlag{
|
||||||
Name: "bzzport",
|
Name: "bzzport",
|
||||||
Usage: "Swarm local http api port",
|
Usage: "Swarm local http api port",
|
||||||
|
|
@ -86,15 +92,23 @@ var (
|
||||||
Name: "swap",
|
Name: "swap",
|
||||||
Usage: "Swarm SWAP enabled (default false)",
|
Usage: "Swarm SWAP enabled (default false)",
|
||||||
}
|
}
|
||||||
|
SwarmSwapAPIFlag = cli.StringFlag{
|
||||||
|
Name: "swap-api",
|
||||||
|
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||||
|
}
|
||||||
SwarmSyncEnabledFlag = cli.BoolTFlag{
|
SwarmSyncEnabledFlag = cli.BoolTFlag{
|
||||||
Name: "sync",
|
Name: "sync",
|
||||||
Usage: "Swarm Syncing enabled (default true)",
|
Usage: "Swarm Syncing enabled (default true)",
|
||||||
}
|
}
|
||||||
EthAPIFlag = cli.StringFlag{
|
EnsAPIFlag = cli.StringFlag{
|
||||||
Name: "ethapi",
|
Name: "ens-api",
|
||||||
Usage: "URL of the Ethereum API provider",
|
Usage: "URL of the Ethereum API provider to use for ENS record lookups",
|
||||||
Value: node.DefaultIPCEndpoint("geth"),
|
Value: node.DefaultIPCEndpoint("geth"),
|
||||||
}
|
}
|
||||||
|
EnsAddrFlag = cli.StringFlag{
|
||||||
|
Name: "ens-addr",
|
||||||
|
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
|
||||||
|
}
|
||||||
SwarmApiFlag = cli.StringFlag{
|
SwarmApiFlag = cli.StringFlag{
|
||||||
Name: "bzzapi",
|
Name: "bzzapi",
|
||||||
Usage: "Swarm HTTP endpoint",
|
Usage: "Swarm HTTP endpoint",
|
||||||
|
|
@ -124,15 +138,30 @@ var (
|
||||||
Name: "corsdomain",
|
Name: "corsdomain",
|
||||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the following flags are deprecated and should be removed in the future
|
||||||
|
DeprecatedEthAPIFlag = cli.StringFlag{
|
||||||
|
Name: "ethapi",
|
||||||
|
Usage: "DEPRECATED: please use --ens-api and --swap-api",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
var defaultNodeConfig = node.DefaultConfig
|
||||||
// Override flag defaults so bzzd can run alongside geth.
|
|
||||||
utils.ListenPortFlag.Value = 30399
|
|
||||||
utils.IPCPathFlag.Value = utils.DirectoryString{Value: "bzzd.ipc"}
|
|
||||||
utils.IPCApiFlag.Value = "admin, bzz, chequebook, debug, rpc, swarmfs, web3"
|
|
||||||
|
|
||||||
// Set up the cli app.
|
// This init function sets defaults so cmd/swarm can run alongside geth.
|
||||||
|
func init() {
|
||||||
|
defaultNodeConfig.Name = clientIdentifier
|
||||||
|
defaultNodeConfig.Version = params.VersionWithCommit(gitCommit)
|
||||||
|
defaultNodeConfig.P2P.ListenAddr = ":30399"
|
||||||
|
defaultNodeConfig.IPCPath = "bzzd.ipc"
|
||||||
|
// Set flag defaults for --help display.
|
||||||
|
utils.ListenPortFlag.Value = 30399
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = utils.NewApp(gitCommit, "Ethereum Swarm")
|
||||||
|
|
||||||
|
// This init function creates the cli.App.
|
||||||
|
func init() {
|
||||||
app.Action = bzzd
|
app.Action = bzzd
|
||||||
app.HideVersion = true // we have a command to print the version
|
app.HideVersion = true // we have a command to print the version
|
||||||
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
||||||
|
|
@ -235,14 +264,17 @@ Cleans database of corrupted entries.
|
||||||
utils.MaxPeersFlag,
|
utils.MaxPeersFlag,
|
||||||
utils.NATFlag,
|
utils.NATFlag,
|
||||||
utils.IPCDisabledFlag,
|
utils.IPCDisabledFlag,
|
||||||
utils.IPCApiFlag,
|
|
||||||
utils.IPCPathFlag,
|
utils.IPCPathFlag,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
// bzzd-specific flags
|
// bzzd-specific flags
|
||||||
CorsStringFlag,
|
CorsStringFlag,
|
||||||
EthAPIFlag,
|
EnsAPIFlag,
|
||||||
|
EnsAddrFlag,
|
||||||
SwarmConfigPathFlag,
|
SwarmConfigPathFlag,
|
||||||
SwarmSwapEnabledFlag,
|
SwarmSwapEnabledFlag,
|
||||||
|
SwarmSwapAPIFlag,
|
||||||
SwarmSyncEnabledFlag,
|
SwarmSyncEnabledFlag,
|
||||||
|
SwarmListenAddrFlag,
|
||||||
SwarmPortFlag,
|
SwarmPortFlag,
|
||||||
SwarmAccountFlag,
|
SwarmAccountFlag,
|
||||||
SwarmNetworkIdFlag,
|
SwarmNetworkIdFlag,
|
||||||
|
|
@ -254,6 +286,8 @@ Cleans database of corrupted entries.
|
||||||
SwarmUploadDefaultPath,
|
SwarmUploadDefaultPath,
|
||||||
SwarmUpFromStdinFlag,
|
SwarmUpFromStdinFlag,
|
||||||
SwarmUploadMimeType,
|
SwarmUploadMimeType,
|
||||||
|
//deprecated flags
|
||||||
|
DeprecatedEthAPIFlag,
|
||||||
}
|
}
|
||||||
app.Flags = append(app.Flags, debug.Flags...)
|
app.Flags = append(app.Flags, debug.Flags...)
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
|
|
@ -275,7 +309,7 @@ func main() {
|
||||||
|
|
||||||
func version(ctx *cli.Context) error {
|
func version(ctx *cli.Context) error {
|
||||||
fmt.Println(strings.Title(clientIdentifier))
|
fmt.Println(strings.Title(clientIdentifier))
|
||||||
fmt.Println("Version:", versionString)
|
fmt.Println("Version:", params.Version)
|
||||||
if gitCommit != "" {
|
if gitCommit != "" {
|
||||||
fmt.Println("Git Commit:", gitCommit)
|
fmt.Println("Git Commit:", gitCommit)
|
||||||
}
|
}
|
||||||
|
|
@ -288,9 +322,21 @@ func version(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func bzzd(ctx *cli.Context) error {
|
func bzzd(ctx *cli.Context) error {
|
||||||
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
|
// exit if the deprecated --ethapi flag is set
|
||||||
|
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
|
||||||
|
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := defaultNodeConfig
|
||||||
|
utils.SetNodeConfig(ctx, &cfg)
|
||||||
|
stack, err := node.New(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("can't create node: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
registerBzzService(ctx, stack)
|
registerBzzService(ctx, stack)
|
||||||
utils.StartNode(stack)
|
utils.StartNode(stack)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
sigc := make(chan os.Signal, 1)
|
sigc := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigc, syscall.SIGTERM)
|
signal.Notify(sigc, syscall.SIGTERM)
|
||||||
|
|
@ -299,6 +345,7 @@ func bzzd(ctx *cli.Context) error {
|
||||||
log.Info("Got sigterm, shutting swarm down...")
|
log.Info("Got sigterm, shutting swarm down...")
|
||||||
stack.Stop()
|
stack.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
|
networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
|
||||||
// Add bootnodes as initial peers.
|
// Add bootnodes as initial peers.
|
||||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
||||||
|
|
@ -314,8 +361,39 @@ func bzzd(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
// detectEnsAddr determines the ENS contract address by getting both the
|
||||||
|
// version and genesis hash using the client and matching them to either
|
||||||
|
// mainnet or testnet addresses
|
||||||
|
func detectEnsAddr(client *rpc.Client) (common.Address, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var version string
|
||||||
|
if err := client.CallContext(ctx, &version, "net_version"); err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0))
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case version == "1" && block.Hash() == params.MainnetGenesisHash:
|
||||||
|
log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress)
|
||||||
|
return ens.MainNetAddress, nil
|
||||||
|
|
||||||
|
case version == "3" && block.Hash() == params.TestnetGenesisHash:
|
||||||
|
log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress)
|
||||||
|
return ens.TestNetAddress, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
||||||
prvkey := getAccount(ctx, stack)
|
prvkey := getAccount(ctx, stack)
|
||||||
|
|
||||||
chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
|
chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
|
||||||
|
|
@ -332,21 +410,54 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
||||||
if len(bzzport) > 0 {
|
if len(bzzport) > 0 {
|
||||||
bzzconfig.Port = bzzport
|
bzzconfig.Port = bzzport
|
||||||
}
|
}
|
||||||
|
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
|
||||||
|
bzzconfig.ListenAddr = bzzaddr
|
||||||
|
}
|
||||||
swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
|
swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
|
||||||
syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name)
|
syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name)
|
||||||
|
|
||||||
ethapi := ctx.GlobalString(EthAPIFlag.Name)
|
swapapi := ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
||||||
|
if swapEnabled && swapapi == "" {
|
||||||
|
utils.Fatalf("SWAP is enabled but --swap-api is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
ensapi := ctx.GlobalString(EnsAPIFlag.Name)
|
||||||
|
ensAddr := ctx.GlobalString(EnsAddrFlag.Name)
|
||||||
|
|
||||||
cors := ctx.GlobalString(CorsStringFlag.Name)
|
cors := ctx.GlobalString(CorsStringFlag.Name)
|
||||||
|
|
||||||
boot := func(ctx *node.ServiceContext) (node.Service, error) {
|
boot := func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
var client *ethclient.Client
|
var swapClient *ethclient.Client
|
||||||
if len(ethapi) > 0 {
|
if swapapi != "" {
|
||||||
client, err = ethclient.Dial(ethapi)
|
log.Info("connecting to SWAP API", "url", swapapi)
|
||||||
|
swapClient, err = ethclient.Dial(swapapi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Can't connect: %v", err)
|
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", swapapi, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return swarm.NewSwarm(ctx, client, bzzconfig, swapEnabled, syncEnabled, cors)
|
|
||||||
|
var ensClient *ethclient.Client
|
||||||
|
if ensapi != "" {
|
||||||
|
log.Info("connecting to ENS API", "url", ensapi)
|
||||||
|
client, err := rpc.Dial(ensapi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error connecting to ENS API %s: %s", ensapi, err)
|
||||||
|
}
|
||||||
|
ensClient = ethclient.NewClient(client)
|
||||||
|
|
||||||
|
if ensAddr != "" {
|
||||||
|
bzzconfig.EnsRoot = common.HexToAddress(ensAddr)
|
||||||
|
} else {
|
||||||
|
ensAddr, err := detectEnsAddr(client)
|
||||||
|
if err == nil {
|
||||||
|
bzzconfig.EnsRoot = ensAddr
|
||||||
|
} else {
|
||||||
|
log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", bzzconfig.EnsRoot), "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, swapEnabled, syncEnabled, cors)
|
||||||
}
|
}
|
||||||
if err := stack.Register(boot); err != nil {
|
if err := stack.Register(boot); err != nil {
|
||||||
utils.Fatalf("Failed to register the Swarm service: %v", err)
|
utils.Fatalf("Failed to register the Swarm service: %v", err)
|
||||||
|
|
@ -368,10 +479,10 @@ func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||||
am := stack.AccountManager()
|
am := stack.AccountManager()
|
||||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
||||||
return decryptStoreAccount(ks, keyid)
|
return decryptStoreAccount(ks, keyid, utils.MakePasswordList(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptStoreAccount(ks *keystore.KeyStore, account string) *ecdsa.PrivateKey {
|
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
|
||||||
var a accounts.Account
|
var a accounts.Account
|
||||||
var err error
|
var err error
|
||||||
if common.IsHexAddress(account) {
|
if common.IsHexAddress(account) {
|
||||||
|
|
@ -392,9 +503,9 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string) *ecdsa.PrivateKe
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Can't load swarm account key: %v", err)
|
utils.Fatalf("Can't load swarm account key: %v", err)
|
||||||
}
|
}
|
||||||
for i := 1; i <= 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
passphrase := promptPassphrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i))
|
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
|
||||||
key, err := keystore.DecryptKey(keyjson, passphrase)
|
key, err := keystore.DecryptKey(keyjson, password)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return key.PrivateKey
|
return key.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +514,18 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string) *ecdsa.PrivateKe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptPassphrase(prompt string) string {
|
// getPassPhrase retrieves the password associated with bzz account, either by fetching
|
||||||
|
// from a list of pre-loaded passwords, or by requesting it interactively from user.
|
||||||
|
func getPassPhrase(prompt string, i int, passwords []string) string {
|
||||||
|
// non-interactive
|
||||||
|
if len(passwords) > 0 {
|
||||||
|
if i < len(passwords) {
|
||||||
|
return passwords[i]
|
||||||
|
}
|
||||||
|
return passwords[len(passwords)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to interactive mode
|
||||||
if prompt != "" {
|
if prompt != "" {
|
||||||
fmt.Println(prompt)
|
fmt.Println(prompt)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of go-ethereum.
|
// This file is part of go-ethereum.
|
||||||
//
|
//
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
|
|
||||||
255
cmd/swarm/run_test.go
Normal file
255
cmd/swarm/run_test.go
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Run the app if we've been exec'd as "swarm-test" in runSwarm.
|
||||||
|
reexec.Register("swarm-test", func() {
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// check if we have been reexec'd
|
||||||
|
if reexec.Init() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
|
||||||
|
tt := cmdtest.NewTestCmd(t, nil)
|
||||||
|
|
||||||
|
// Boot "swarm". This actually runs the test binary but the TestMain
|
||||||
|
// function will prevent any tests from running.
|
||||||
|
tt.Run("swarm-test", args...)
|
||||||
|
|
||||||
|
return tt
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCluster struct {
|
||||||
|
Nodes []*testNode
|
||||||
|
TmpDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTestCluster starts a test swarm cluster of the given size.
|
||||||
|
//
|
||||||
|
// A temporary directory is created and each node gets a data directory inside
|
||||||
|
// it.
|
||||||
|
//
|
||||||
|
// Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
|
||||||
|
// ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
|
||||||
|
// as flags).
|
||||||
|
//
|
||||||
|
// When starting more than one node, they are connected together using the
|
||||||
|
// admin SetPeer RPC method.
|
||||||
|
func newTestCluster(t *testing.T, size int) *testCluster {
|
||||||
|
cluster := &testCluster{}
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
cluster.Shutdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
tmpdir, err := ioutil.TempDir("", "swarm-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cluster.TmpDir = tmpdir
|
||||||
|
|
||||||
|
// start the nodes
|
||||||
|
cluster.Nodes = make([]*testNode, 0, size)
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
dir := filepath.Join(cluster.TmpDir, fmt.Sprintf("swarm%02d", i))
|
||||||
|
if err := os.Mkdir(dir, 0700); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
node := newTestNode(t, dir)
|
||||||
|
node.Name = fmt.Sprintf("swarm%02d", i)
|
||||||
|
|
||||||
|
cluster.Nodes = append(cluster.Nodes, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size == 1 {
|
||||||
|
return cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect the nodes together
|
||||||
|
for _, node := range cluster.Nodes {
|
||||||
|
if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait until all nodes have the correct number of peers
|
||||||
|
outer:
|
||||||
|
for _, node := range cluster.Nodes {
|
||||||
|
var peers []*p2p.PeerInfo
|
||||||
|
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
|
||||||
|
if err := node.Client.Call(&peers, "admin_peers"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(peers) == len(cluster.Nodes)-1 {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCluster) Shutdown() {
|
||||||
|
for _, node := range c.Nodes {
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
os.RemoveAll(c.TmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testNode struct {
|
||||||
|
Name string
|
||||||
|
Addr string
|
||||||
|
URL string
|
||||||
|
Enode string
|
||||||
|
Dir string
|
||||||
|
Client *rpc.Client
|
||||||
|
Cmd *cmdtest.TestCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
const testPassphrase = "swarm-test-passphrase"
|
||||||
|
|
||||||
|
func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
|
// create key
|
||||||
|
conf := &node.Config{
|
||||||
|
DataDir: dir,
|
||||||
|
IPCPath: "bzzd.ipc",
|
||||||
|
}
|
||||||
|
n, err := node.New(conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
account, err := n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
node := &testNode{Dir: dir}
|
||||||
|
|
||||||
|
// use a unique IPCPath when running tests on Windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// assign ports
|
||||||
|
httpPort, err := assignTCPPort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
p2pPort, err := assignTCPPort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the node
|
||||||
|
node.Cmd = runSwarm(t,
|
||||||
|
"--port", p2pPort,
|
||||||
|
"--nodiscover",
|
||||||
|
"--datadir", dir,
|
||||||
|
"--ipcpath", conf.IPCPath,
|
||||||
|
"--ens-api", "",
|
||||||
|
"--bzzaccount", account.Address.String(),
|
||||||
|
"--bzznetworkid", "321",
|
||||||
|
"--bzzport", httpPort,
|
||||||
|
"--verbosity", "6",
|
||||||
|
)
|
||||||
|
node.Cmd.InputLine(testPassphrase)
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// wait for the node to start
|
||||||
|
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||||
|
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Client == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load info
|
||||||
|
var info swarm.Info
|
||||||
|
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
|
||||||
|
node.URL = "http://" + node.Addr
|
||||||
|
|
||||||
|
var nodeInfo p2p.NodeInfo
|
||||||
|
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *testNode) Shutdown() {
|
||||||
|
if n.Cmd != nil {
|
||||||
|
n.Cmd.Kill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignTCPPort() (string, error) {
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
l.Close()
|
||||||
|
_, port, err := net.SplitHostPort(l.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -87,24 +88,32 @@ func upload(ctx *cli.Context) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Error opening file: %s", err)
|
utils.Fatalf("Error opening file: %s", err)
|
||||||
}
|
}
|
||||||
var hash string
|
|
||||||
|
// define a function which either uploads a directory or single file
|
||||||
|
// based on the type of the file being uploaded
|
||||||
|
var doUpload func() (hash string, err error)
|
||||||
if stat.IsDir() {
|
if stat.IsDir() {
|
||||||
|
doUpload = func() (string, error) {
|
||||||
if !recursive {
|
if !recursive {
|
||||||
utils.Fatalf("Argument is a directory and recursive upload is disabled")
|
return "", errors.New("Argument is a directory and recursive upload is disabled")
|
||||||
|
}
|
||||||
|
return client.UploadDirectory(file, defaultPath, "")
|
||||||
}
|
}
|
||||||
hash, err = client.UploadDirectory(file, defaultPath, "")
|
|
||||||
} else {
|
} else {
|
||||||
|
doUpload = func() (string, error) {
|
||||||
|
f, err := swarm.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error opening file: %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
if mimeType == "" {
|
if mimeType == "" {
|
||||||
mimeType = detectMimeType(file)
|
mimeType = detectMimeType(file)
|
||||||
}
|
}
|
||||||
f, err := swarm.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error opening file: %s", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
f.ContentType = mimeType
|
f.ContentType = mimeType
|
||||||
hash, err = client.Upload(f, "")
|
return client.Upload(f, "")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
hash, err := doUpload()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Upload failed: %s", err)
|
utils.Fatalf("Upload failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
cmd/swarm/upload_test.go
Normal file
78
cmd/swarm/upload_test.go
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestCLISwarmUp tests that running 'swarm up' makes the resulting file
|
||||||
|
// available from all nodes via the HTTP API
|
||||||
|
func TestCLISwarmUp(t *testing.T) {
|
||||||
|
t.Skip("flaky test")
|
||||||
|
|
||||||
|
// start 3 node cluster
|
||||||
|
t.Log("starting 3 node cluster")
|
||||||
|
cluster := newTestCluster(t, 3)
|
||||||
|
defer cluster.Shutdown()
|
||||||
|
|
||||||
|
// create a tmp file
|
||||||
|
tmp, err := ioutil.TempFile("", "swarm-test")
|
||||||
|
assertNil(t, err)
|
||||||
|
defer tmp.Close()
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
_, err = io.WriteString(tmp, "data")
|
||||||
|
assertNil(t, err)
|
||||||
|
|
||||||
|
// upload the file with 'swarm up' and expect a hash
|
||||||
|
t.Log("uploading file with 'swarm up'")
|
||||||
|
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", tmp.Name())
|
||||||
|
_, matches := up.ExpectRegexp(`[a-f\d]{64}`)
|
||||||
|
up.ExpectExit()
|
||||||
|
hash := matches[0]
|
||||||
|
t.Logf("file uploaded with hash %s", hash)
|
||||||
|
|
||||||
|
// get the file from the HTTP API of each node
|
||||||
|
for _, node := range cluster.Nodes {
|
||||||
|
t.Logf("getting file from %s", node.Name)
|
||||||
|
res, err := http.Get(node.URL + "/bzz:/" + hash)
|
||||||
|
assertNil(t, err)
|
||||||
|
assertHTTPResponse(t, res, http.StatusOK, "data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNil(t *testing.T, err error) {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertHTTPResponse(t *testing.T, res *http.Response, expectedStatus int, expectedBody string) {
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != expectedStatus {
|
||||||
|
t.Fatalf("expected HTTP status %d, got %s", expectedStatus, res.Status)
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
assertNil(t, err)
|
||||||
|
if string(data) != expectedBody {
|
||||||
|
t.Fatalf("expected HTTP body %q, got %q", expectedBody, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -78,6 +79,58 @@ func (self DirectoryFlag) Apply(set *flag.FlagSet) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TextMarshaler interface {
|
||||||
|
encoding.TextMarshaler
|
||||||
|
encoding.TextUnmarshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
// textMarshalerVal turns a TextMarshaler into a flag.Value
|
||||||
|
type textMarshalerVal struct {
|
||||||
|
v TextMarshaler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v textMarshalerVal) String() string {
|
||||||
|
if v.v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
text, _ := v.v.MarshalText()
|
||||||
|
return string(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v textMarshalerVal) Set(s string) error {
|
||||||
|
return v.v.UnmarshalText([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextMarshalerFlag wraps a TextMarshaler value.
|
||||||
|
type TextMarshalerFlag struct {
|
||||||
|
Name string
|
||||||
|
Value TextMarshaler
|
||||||
|
Usage string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f TextMarshalerFlag) GetName() string {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f TextMarshalerFlag) String() string {
|
||||||
|
return fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f TextMarshalerFlag) Apply(set *flag.FlagSet) {
|
||||||
|
eachName(f.Name, func(name string) {
|
||||||
|
set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalTextMarshaler returns the value of a TextMarshalerFlag from the global flag set.
|
||||||
|
func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler {
|
||||||
|
val := ctx.GlobalGeneric(name)
|
||||||
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return val.(textMarshalerVal).v
|
||||||
|
}
|
||||||
|
|
||||||
// BigFlag is a command line flag that accepts 256 bit big integers in decimal or
|
// BigFlag is a command line flag that accepts 256 bit big integers in decimal or
|
||||||
// hexadecimal syntax.
|
// hexadecimal syntax.
|
||||||
type BigFlag struct {
|
type BigFlag struct {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of go-ethereum.
|
// This file is part of go-ethereum.
|
||||||
//
|
//
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -27,7 +27,9 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -46,7 +48,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const quitCommand = "~Q"
|
const quitCommand = "~Q"
|
||||||
const symKeyName = "da919ea33001b04dfc630522e33078ec0df11"
|
|
||||||
|
|
||||||
// singletons
|
// singletons
|
||||||
var (
|
var (
|
||||||
|
|
@ -65,6 +66,7 @@ var (
|
||||||
asymKey *ecdsa.PrivateKey
|
asymKey *ecdsa.PrivateKey
|
||||||
nodeid *ecdsa.PrivateKey
|
nodeid *ecdsa.PrivateKey
|
||||||
topic whisper.TopicType
|
topic whisper.TopicType
|
||||||
|
asymKeyID string
|
||||||
filterID string
|
filterID string
|
||||||
symPass string
|
symPass string
|
||||||
msPassword string
|
msPassword string
|
||||||
|
|
@ -72,20 +74,22 @@ var (
|
||||||
|
|
||||||
// cmd arguments
|
// cmd arguments
|
||||||
var (
|
var (
|
||||||
echoMode = flag.Bool("e", false, "echo mode: prints some arguments for diagnostics")
|
bootstrapMode = flag.Bool("standalone", false, "boostrap node: don't actively connect to peers, wait for incoming connections")
|
||||||
bootstrapMode = flag.Bool("b", false, "boostrap node: don't actively connect to peers, wait for incoming connections")
|
forwarderMode = flag.Bool("forwarder", false, "forwarder mode: only forward messages, neither send nor decrypt messages")
|
||||||
forwarderMode = flag.Bool("f", false, "forwarder mode: only forward messages, neither send nor decrypt messages")
|
mailServerMode = flag.Bool("mailserver", false, "mail server mode: delivers expired messages on demand")
|
||||||
mailServerMode = flag.Bool("s", false, "mail server mode: delivers expired messages on demand")
|
requestMail = flag.Bool("mailclient", false, "request expired messages from the bootstrap server")
|
||||||
requestMail = flag.Bool("r", false, "request expired messages from the bootstrap server")
|
asymmetricMode = flag.Bool("asym", false, "use asymmetric encryption")
|
||||||
asymmetricMode = flag.Bool("a", false, "use asymmetric encryption")
|
generateKey = flag.Bool("generatekey", false, "generate and show the private key")
|
||||||
testMode = flag.Bool("t", false, "use of predefined parameters for diagnostics")
|
fileExMode = flag.Bool("fileexchange", false, "file exchange mode")
|
||||||
generateKey = flag.Bool("k", false, "generate and show the private key")
|
testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics")
|
||||||
|
echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics")
|
||||||
|
|
||||||
argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level")
|
argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level")
|
||||||
argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
|
argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds")
|
||||||
argWorkTime = flag.Uint("work", 5, "work time in seconds")
|
argWorkTime = flag.Uint("work", 5, "work time in seconds")
|
||||||
argPoW = flag.Float64("pow", whisper.MinimumPoW, "PoW for normal messages in float format (e.g. 2.7)")
|
argMaxSize = flag.Uint("maxsize", uint(whisper.DefaultMaxMessageSize), "max size of message")
|
||||||
argServerPoW = flag.Float64("mspow", whisper.MinimumPoW, "PoW requirement for Mail Server request")
|
argPoW = flag.Float64("pow", whisper.DefaultMinimumPoW, "PoW for normal messages in float format (e.g. 2.7)")
|
||||||
|
argServerPoW = flag.Float64("mspow", whisper.DefaultMinimumPoW, "PoW requirement for Mail Server request")
|
||||||
|
|
||||||
argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)")
|
argIP = flag.String("ip", "", "IP address and port of this node (e.g. 127.0.0.1:30303)")
|
||||||
argPub = flag.String("pub", "", "public key for asymmetric encryption")
|
argPub = flag.String("pub", "", "public key for asymmetric encryption")
|
||||||
|
|
@ -93,6 +97,7 @@ var (
|
||||||
argIDFile = flag.String("idfile", "", "file name with node id (private key)")
|
argIDFile = flag.String("idfile", "", "file name with node id (private key)")
|
||||||
argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)")
|
argEnode = flag.String("boot", "", "bootstrap node you want to connect to (e.g. enode://e454......08d50@52.176.211.200:16428)")
|
||||||
argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)")
|
argTopic = flag.String("topic", "", "topic in hexadecimal format (e.g. 70a4beef)")
|
||||||
|
argSaveDir = flag.String("savedir", "", "directory where incoming messages will be saved as files")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -134,6 +139,14 @@ func processArgs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(*argSaveDir) > 0 {
|
||||||
|
if _, err := os.Stat(*argSaveDir); os.IsNotExist(err) {
|
||||||
|
utils.Fatalf("Download directory '%s' does not exist", *argSaveDir)
|
||||||
|
}
|
||||||
|
} else if *fileExMode {
|
||||||
|
utils.Fatalf("Parameter 'savedir' is mandatory for file exchange mode")
|
||||||
|
}
|
||||||
|
|
||||||
if *echoMode {
|
if *echoMode {
|
||||||
echo()
|
echo()
|
||||||
}
|
}
|
||||||
|
|
@ -170,7 +183,7 @@ func initialize() {
|
||||||
|
|
||||||
if *testMode {
|
if *testMode {
|
||||||
symPass = "wwww" // ascii code: 0x77777777
|
symPass = "wwww" // ascii code: 0x77777777
|
||||||
msPassword = "mail server test password"
|
msPassword = "wwww"
|
||||||
}
|
}
|
||||||
|
|
||||||
if *bootstrapMode {
|
if *bootstrapMode {
|
||||||
|
|
@ -185,6 +198,11 @@ func initialize() {
|
||||||
peers = append(peers, peer)
|
peers = append(peers, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := &whisper.Config{
|
||||||
|
MaxMessageSize: uint32(*argMaxSize),
|
||||||
|
MinimumAcceptedPOW: *argPoW,
|
||||||
|
}
|
||||||
|
|
||||||
if *mailServerMode {
|
if *mailServerMode {
|
||||||
if len(msPassword) == 0 {
|
if len(msPassword) == 0 {
|
||||||
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
msPassword, err = console.Stdin.PromptPassword("Please enter the Mail Server password: ")
|
||||||
|
|
@ -192,16 +210,48 @@ func initialize() {
|
||||||
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
utils.Fatalf("Failed to read Mail Server password: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shh = whisper.New()
|
|
||||||
|
shh = whisper.New(cfg)
|
||||||
shh.RegisterServer(&mailServer)
|
shh.RegisterServer(&mailServer)
|
||||||
mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW)
|
mailServer.Init(shh, *argDBPath, msPassword, *argServerPoW)
|
||||||
} else {
|
} else {
|
||||||
shh = whisper.New()
|
shh = whisper.New(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *argPoW != whisper.DefaultMinimumPoW {
|
||||||
|
err := shh.SetMinimumPoW(*argPoW)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to set PoW: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint32(*argMaxSize) != whisper.DefaultMaxMessageSize {
|
||||||
|
err := shh.SetMaxMessageSize(uint32(*argMaxSize))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to set max message size: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asymKeyID, err = shh.NewKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to generate a new key pair: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
asymKey, err = shh.GetPrivateKey(asymKeyID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to retrieve a new key pair: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
asymKey = shh.NewIdentity()
|
|
||||||
if nodeid == nil {
|
if nodeid == nil {
|
||||||
nodeid = shh.NewIdentity()
|
tmpID, err := shh.NewKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to generate a new key pair: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeid, err = shh.GetPrivateKey(tmpID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to retrieve a new key pair: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxPeers := 80
|
maxPeers := 80
|
||||||
|
|
@ -213,7 +263,7 @@ func initialize() {
|
||||||
Config: p2p.Config{
|
Config: p2p.Config{
|
||||||
PrivateKey: nodeid,
|
PrivateKey: nodeid,
|
||||||
MaxPeers: maxPeers,
|
MaxPeers: maxPeers,
|
||||||
Name: common.MakeName("whisper-go", "5.0"),
|
Name: common.MakeName("wnode", "5.0"),
|
||||||
Protocols: shh.Protocols(),
|
Protocols: shh.Protocols(),
|
||||||
ListenAddr: *argIP,
|
ListenAddr: *argIP,
|
||||||
NAT: nat.Any(),
|
NAT: nat.Any(),
|
||||||
|
|
@ -263,7 +313,11 @@ func configureNode() {
|
||||||
if *asymmetricMode {
|
if *asymmetricMode {
|
||||||
if len(*argPub) == 0 {
|
if len(*argPub) == 0 {
|
||||||
s := scanLine("Please enter the peer's public key: ")
|
s := scanLine("Please enter the peer's public key: ")
|
||||||
pub = crypto.ToECDSAPub(common.FromHex(s))
|
b := common.FromHex(s)
|
||||||
|
if b == nil {
|
||||||
|
utils.Fatalf("Error: can not convert hexadecimal string")
|
||||||
|
}
|
||||||
|
pub = crypto.ToECDSAPub(b)
|
||||||
if !isKeyValid(pub) {
|
if !isKeyValid(pub) {
|
||||||
utils.Fatalf("Error: invalid public key")
|
utils.Fatalf("Error: invalid public key")
|
||||||
}
|
}
|
||||||
|
|
@ -282,17 +336,25 @@ func configureNode() {
|
||||||
|
|
||||||
if !*asymmetricMode && !*forwarderMode {
|
if !*asymmetricMode && !*forwarderMode {
|
||||||
if len(symPass) == 0 {
|
if len(symPass) == 0 {
|
||||||
symPass, err = console.Stdin.PromptPassword("Please enter the password: ")
|
symPass, err = console.Stdin.PromptPassword("Please enter the password for symmetric encryption: ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shh.AddSymKey(symKeyName, []byte(symPass))
|
symKeyID, err := shh.AddSymKeyFromPassword(symPass)
|
||||||
symKey = shh.GetSymKey(symKeyName)
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to create symmetric key: %s", err)
|
||||||
|
}
|
||||||
|
symKey, err = shh.GetSymKey(symKeyID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to save symmetric key: %s", err)
|
||||||
|
}
|
||||||
if len(*argTopic) == 0 {
|
if len(*argTopic) == 0 {
|
||||||
generateTopic([]byte(symPass))
|
generateTopic([]byte(symPass))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Filter is configured for the topic: %x \n", topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *mailServerMode {
|
if *mailServerMode {
|
||||||
|
|
@ -304,18 +366,17 @@ func configureNode() {
|
||||||
filter := whisper.Filter{
|
filter := whisper.Filter{
|
||||||
KeySym: symKey,
|
KeySym: symKey,
|
||||||
KeyAsym: asymKey,
|
KeyAsym: asymKey,
|
||||||
Topics: []whisper.TopicType{topic},
|
Topics: [][]byte{topic[:]},
|
||||||
AcceptP2P: p2pAccept,
|
AllowP2P: p2pAccept,
|
||||||
}
|
}
|
||||||
filterID, err = shh.Watch(&filter)
|
filterID, err = shh.Subscribe(&filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to install filter: %s", err)
|
utils.Fatalf("Failed to install filter: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Filter is configured for the topic: %x \n", topic)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateTopic(password []byte) {
|
func generateTopic(password []byte) {
|
||||||
x := pbkdf2.Key(password, password, 8196, 128, sha512.New)
|
x := pbkdf2.Key(password, password, 4096, 128, sha512.New)
|
||||||
for i := 0; i < len(x); i++ {
|
for i := 0; i < len(x); i++ {
|
||||||
topic[i%whisper.TopicLength] ^= x[i]
|
topic[i%whisper.TopicLength] ^= x[i]
|
||||||
}
|
}
|
||||||
|
|
@ -351,6 +412,8 @@ func run() {
|
||||||
|
|
||||||
if *requestMail {
|
if *requestMail {
|
||||||
requestExpiredMessagesLoop()
|
requestExpiredMessagesLoop()
|
||||||
|
} else if *fileExMode {
|
||||||
|
sendFilesLoop()
|
||||||
} else {
|
} else {
|
||||||
sendLoop()
|
sendLoop()
|
||||||
}
|
}
|
||||||
|
|
@ -376,6 +439,31 @@ func sendLoop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendFilesLoop() {
|
||||||
|
for {
|
||||||
|
s := scanLine("")
|
||||||
|
if s == quitCommand {
|
||||||
|
fmt.Println("Quit command received")
|
||||||
|
close(done)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadFile(s)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf(">>> Error: %s \n", err)
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
h := sendMsg(b)
|
||||||
|
if (h == common.Hash{}) {
|
||||||
|
fmt.Printf(">>> Error: message was not sent \n")
|
||||||
|
} else {
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
from := crypto.PubkeyToAddress(asymKey.PublicKey)
|
||||||
|
fmt.Printf("\n%d <%x>: sent message with hash %x\n", timestamp, from, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func scanLine(prompt string) string {
|
func scanLine(prompt string) string {
|
||||||
if len(prompt) > 0 {
|
if len(prompt) > 0 {
|
||||||
fmt.Print(prompt)
|
fmt.Print(prompt)
|
||||||
|
|
@ -402,7 +490,7 @@ func scanUint(prompt string) uint32 {
|
||||||
return uint32(i)
|
return uint32(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMsg(payload []byte) {
|
func sendMsg(payload []byte) common.Hash {
|
||||||
params := whisper.MessageParams{
|
params := whisper.MessageParams{
|
||||||
Src: asymKey,
|
Src: asymKey,
|
||||||
Dst: pub,
|
Dst: pub,
|
||||||
|
|
@ -414,17 +502,23 @@ func sendMsg(payload []byte) {
|
||||||
WorkTime: uint32(*argWorkTime),
|
WorkTime: uint32(*argWorkTime),
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := whisper.NewSentMessage(¶ms)
|
msg, err := whisper.NewSentMessage(¶ms)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("failed to create new message: %s", err)
|
||||||
|
}
|
||||||
envelope, err := msg.Wrap(¶ms)
|
envelope, err := msg.Wrap(¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to seal message: %v \n", err)
|
fmt.Printf("failed to seal message: %v \n", err)
|
||||||
return
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = shh.Send(envelope)
|
err = shh.Send(envelope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to send message: %v \n", err)
|
fmt.Printf("failed to send message: %v \n", err)
|
||||||
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return envelope.Hash()
|
||||||
}
|
}
|
||||||
|
|
||||||
func messageLoop() {
|
func messageLoop() {
|
||||||
|
|
@ -440,8 +534,12 @@ func messageLoop() {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
messages := f.Retrieve()
|
messages := f.Retrieve()
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
|
if *fileExMode || len(msg.Payload) > 2048 {
|
||||||
|
writeMessageToFile(*argSaveDir, msg)
|
||||||
|
} else {
|
||||||
printMessageInfo(msg)
|
printMessageInfo(msg)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -464,19 +562,47 @@ func printMessageInfo(msg *whisper.ReceivedMessage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeMessageToFile(dir string, msg *whisper.ReceivedMessage) {
|
||||||
|
timestamp := fmt.Sprintf("%d", msg.Sent)
|
||||||
|
name := fmt.Sprintf("%x", msg.EnvelopeHash)
|
||||||
|
|
||||||
|
var address common.Address
|
||||||
|
if msg.Src != nil {
|
||||||
|
address = crypto.PubkeyToAddress(*msg.Src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if whisper.IsPubKeyEqual(msg.Src, &asymKey.PublicKey) {
|
||||||
|
// message from myself: don't save, only report
|
||||||
|
fmt.Printf("\n%s <%x>: message received: '%s'\n", timestamp, address, name)
|
||||||
|
} else if len(dir) > 0 {
|
||||||
|
fullpath := filepath.Join(dir, name)
|
||||||
|
err := ioutil.WriteFile(fullpath, msg.Payload, 0644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("\n%s {%x}: message received but not saved: %s\n", timestamp, address, err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\n%s {%x}: message received and saved as '%s' (%d bytes)\n", timestamp, address, name, len(msg.Payload))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\n%s {%x}: big message received (%d bytes), but not saved: %s\n", timestamp, address, len(msg.Payload), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func requestExpiredMessagesLoop() {
|
func requestExpiredMessagesLoop() {
|
||||||
var key, peerID []byte
|
var key, peerID []byte
|
||||||
var timeLow, timeUpp uint32
|
var timeLow, timeUpp uint32
|
||||||
var t string
|
var t string
|
||||||
var xt, empty whisper.TopicType
|
var xt, empty whisper.TopicType
|
||||||
|
|
||||||
err := shh.AddSymKey(mailserver.MailServerKeyName, []byte(msPassword))
|
keyID, err := shh.AddSymKeyFromPassword(msPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
|
utils.Fatalf("Failed to create symmetric key for mail request: %s", err)
|
||||||
}
|
}
|
||||||
key = shh.GetSymKey(mailserver.MailServerKeyName)
|
key, err = shh.GetSymKey(keyID)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to save symmetric key for mail request: %s", err)
|
||||||
|
}
|
||||||
peerID = extractIdFromEnode(*argEnode)
|
peerID = extractIdFromEnode(*argEnode)
|
||||||
shh.MarkPeerTrusted(peerID)
|
shh.AllowP2PMessagesFromPeer(peerID)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
|
timeLow = scanUint("Please enter the lower limit of the time range (unix timestamp): ")
|
||||||
|
|
@ -508,7 +634,10 @@ func requestExpiredMessagesLoop() {
|
||||||
params.Src = nodeid
|
params.Src = nodeid
|
||||||
params.WorkTime = 5
|
params.WorkTime = 5
|
||||||
|
|
||||||
msg := whisper.NewSentMessage(¶ms)
|
msg, err := whisper.NewSentMessage(¶ms)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("failed to create new message: %s", err)
|
||||||
|
}
|
||||||
env, err := msg.Wrap(¶ms)
|
env, err := msg.Wrap(¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Wrap failed: %s", err)
|
utils.Fatalf("Wrap failed: %s", err)
|
||||||
|
|
@ -527,7 +656,6 @@ func extractIdFromEnode(s string) []byte {
|
||||||
n, err := discover.ParseNode(s)
|
n, err := discover.ParseNode(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to parse enode: %s", err)
|
utils.Fatalf("Failed to parse enode: %s", err)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
return n.ID[:]
|
return n.ID[:]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
188
common/bitutil/bitutil.go
Normal file
188
common/bitutil/bitutil.go
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Adapted from: https://golang.org/src/crypto/cipher/xor.go
|
||||||
|
|
||||||
|
// Package bitutil implements fast bitwise operations.
|
||||||
|
package bitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||||
|
const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x"
|
||||||
|
|
||||||
|
// XORBytes xors the bytes in a and b. The destination is assumed to have enough
|
||||||
|
// space. Returns the number of bytes xor'd.
|
||||||
|
func XORBytes(dst, a, b []byte) int {
|
||||||
|
if supportsUnaligned {
|
||||||
|
return fastXORBytes(dst, a, b)
|
||||||
|
}
|
||||||
|
return safeXORBytes(dst, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fastXORBytes xors in bulk. It only works on architectures that support
|
||||||
|
// unaligned read/writes.
|
||||||
|
func fastXORBytes(dst, a, b []byte) int {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
w := n / wordSize
|
||||||
|
if w > 0 {
|
||||||
|
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
||||||
|
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
||||||
|
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
dw[i] = aw[i] ^ bw[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := (n - n%wordSize); i < n; i++ {
|
||||||
|
dst[i] = a[i] ^ b[i]
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeXORBytes xors one by one. It works on all architectures, independent if
|
||||||
|
// it supports unaligned read/writes or not.
|
||||||
|
func safeXORBytes(dst, a, b []byte) int {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
dst[i] = a[i] ^ b[i]
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// ANDBytes ands the bytes in a and b. The destination is assumed to have enough
|
||||||
|
// space. Returns the number of bytes and'd.
|
||||||
|
func ANDBytes(dst, a, b []byte) int {
|
||||||
|
if supportsUnaligned {
|
||||||
|
return fastANDBytes(dst, a, b)
|
||||||
|
}
|
||||||
|
return safeANDBytes(dst, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fastANDBytes ands in bulk. It only works on architectures that support
|
||||||
|
// unaligned read/writes.
|
||||||
|
func fastANDBytes(dst, a, b []byte) int {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
w := n / wordSize
|
||||||
|
if w > 0 {
|
||||||
|
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
||||||
|
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
||||||
|
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
dw[i] = aw[i] & bw[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := (n - n%wordSize); i < n; i++ {
|
||||||
|
dst[i] = a[i] & b[i]
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeANDBytes ands one by one. It works on all architectures, independent if
|
||||||
|
// it supports unaligned read/writes or not.
|
||||||
|
func safeANDBytes(dst, a, b []byte) int {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
dst[i] = a[i] & b[i]
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// ORBytes ors the bytes in a and b. The destination is assumed to have enough
|
||||||
|
// space. Returns the number of bytes or'd.
|
||||||
|
func ORBytes(dst, a, b []byte) int {
|
||||||
|
if supportsUnaligned {
|
||||||
|
return fastORBytes(dst, a, b)
|
||||||
|
}
|
||||||
|
return safeORBytes(dst, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fastORBytes ors in bulk. It only works on architectures that support
|
||||||
|
// unaligned read/writes.
|
||||||
|
func fastORBytes(dst, a, b []byte) int {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
w := n / wordSize
|
||||||
|
if w > 0 {
|
||||||
|
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
||||||
|
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
||||||
|
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
dw[i] = aw[i] | bw[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := (n - n%wordSize); i < n; i++ {
|
||||||
|
dst[i] = a[i] | b[i]
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeORBytes ors one by one. It works on all architectures, independent if
|
||||||
|
// it supports unaligned read/writes or not.
|
||||||
|
func safeORBytes(dst, a, b []byte) int {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
dst[i] = a[i] | b[i]
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBytes tests whether any bit is set in the input byte slice.
|
||||||
|
func TestBytes(p []byte) bool {
|
||||||
|
if supportsUnaligned {
|
||||||
|
return fastTestBytes(p)
|
||||||
|
}
|
||||||
|
return safeTestBytes(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fastTestBytes tests for set bits in bulk. It only works on architectures that
|
||||||
|
// support unaligned read/writes.
|
||||||
|
func fastTestBytes(p []byte) bool {
|
||||||
|
n := len(p)
|
||||||
|
w := n / wordSize
|
||||||
|
if w > 0 {
|
||||||
|
pw := *(*[]uintptr)(unsafe.Pointer(&p))
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
if pw[i] != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := (n - n%wordSize); i < n; i++ {
|
||||||
|
if p[i] != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeTestBytes tests for set bits one byte at a time. It works on all
|
||||||
|
// architectures, independent if it supports unaligned read/writes or not.
|
||||||
|
func safeTestBytes(p []byte) bool {
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
if p[i] != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
215
common/bitutil/bitutil_test.go
Normal file
215
common/bitutil/bitutil_test.go
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Adapted from: https://golang.org/src/crypto/cipher/xor_test.go
|
||||||
|
|
||||||
|
package bitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that bitwise XOR works for various alignments.
|
||||||
|
func TestXOR(t *testing.T) {
|
||||||
|
for alignP := 0; alignP < 2; alignP++ {
|
||||||
|
for alignQ := 0; alignQ < 2; alignQ++ {
|
||||||
|
for alignD := 0; alignD < 2; alignD++ {
|
||||||
|
p := make([]byte, 1023)[alignP:]
|
||||||
|
q := make([]byte, 1023)[alignQ:]
|
||||||
|
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
p[i] = byte(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(q); i++ {
|
||||||
|
q[i] = byte(len(q) - i)
|
||||||
|
}
|
||||||
|
d1 := make([]byte, 1023+alignD)[alignD:]
|
||||||
|
d2 := make([]byte, 1023+alignD)[alignD:]
|
||||||
|
|
||||||
|
XORBytes(d1, p, q)
|
||||||
|
safeXORBytes(d2, p, q)
|
||||||
|
if !bytes.Equal(d1, d2) {
|
||||||
|
t.Error("not equal", d1, d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that bitwise AND works for various alignments.
|
||||||
|
func TestAND(t *testing.T) {
|
||||||
|
for alignP := 0; alignP < 2; alignP++ {
|
||||||
|
for alignQ := 0; alignQ < 2; alignQ++ {
|
||||||
|
for alignD := 0; alignD < 2; alignD++ {
|
||||||
|
p := make([]byte, 1023)[alignP:]
|
||||||
|
q := make([]byte, 1023)[alignQ:]
|
||||||
|
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
p[i] = byte(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(q); i++ {
|
||||||
|
q[i] = byte(len(q) - i)
|
||||||
|
}
|
||||||
|
d1 := make([]byte, 1023+alignD)[alignD:]
|
||||||
|
d2 := make([]byte, 1023+alignD)[alignD:]
|
||||||
|
|
||||||
|
ANDBytes(d1, p, q)
|
||||||
|
safeANDBytes(d2, p, q)
|
||||||
|
if !bytes.Equal(d1, d2) {
|
||||||
|
t.Error("not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that bitwise OR works for various alignments.
|
||||||
|
func TestOR(t *testing.T) {
|
||||||
|
for alignP := 0; alignP < 2; alignP++ {
|
||||||
|
for alignQ := 0; alignQ < 2; alignQ++ {
|
||||||
|
for alignD := 0; alignD < 2; alignD++ {
|
||||||
|
p := make([]byte, 1023)[alignP:]
|
||||||
|
q := make([]byte, 1023)[alignQ:]
|
||||||
|
|
||||||
|
for i := 0; i < len(p); i++ {
|
||||||
|
p[i] = byte(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(q); i++ {
|
||||||
|
q[i] = byte(len(q) - i)
|
||||||
|
}
|
||||||
|
d1 := make([]byte, 1023+alignD)[alignD:]
|
||||||
|
d2 := make([]byte, 1023+alignD)[alignD:]
|
||||||
|
|
||||||
|
ORBytes(d1, p, q)
|
||||||
|
safeORBytes(d2, p, q)
|
||||||
|
if !bytes.Equal(d1, d2) {
|
||||||
|
t.Error("not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that bit testing works for various alignments.
|
||||||
|
func TestTest(t *testing.T) {
|
||||||
|
for align := 0; align < 2; align++ {
|
||||||
|
// Test for bits set in the bulk part
|
||||||
|
p := make([]byte, 1023)[align:]
|
||||||
|
p[100] = 1
|
||||||
|
|
||||||
|
if TestBytes(p) != safeTestBytes(p) {
|
||||||
|
t.Error("not equal")
|
||||||
|
}
|
||||||
|
// Test for bits set in the tail part
|
||||||
|
q := make([]byte, 1023)[align:]
|
||||||
|
q[len(q)-1] = 1
|
||||||
|
|
||||||
|
if TestBytes(q) != safeTestBytes(q) {
|
||||||
|
t.Error("not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the potentially optimized XOR performance.
|
||||||
|
func BenchmarkFastXOR1KB(b *testing.B) { benchmarkFastXOR(b, 1024) }
|
||||||
|
func BenchmarkFastXOR2KB(b *testing.B) { benchmarkFastXOR(b, 2048) }
|
||||||
|
func BenchmarkFastXOR4KB(b *testing.B) { benchmarkFastXOR(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkFastXOR(b *testing.B, size int) {
|
||||||
|
p, q := make([]byte, size), make([]byte, size)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
XORBytes(p, p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the baseline XOR performance.
|
||||||
|
func BenchmarkBaseXOR1KB(b *testing.B) { benchmarkBaseXOR(b, 1024) }
|
||||||
|
func BenchmarkBaseXOR2KB(b *testing.B) { benchmarkBaseXOR(b, 2048) }
|
||||||
|
func BenchmarkBaseXOR4KB(b *testing.B) { benchmarkBaseXOR(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkBaseXOR(b *testing.B, size int) {
|
||||||
|
p, q := make([]byte, size), make([]byte, size)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
safeXORBytes(p, p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the potentially optimized AND performance.
|
||||||
|
func BenchmarkFastAND1KB(b *testing.B) { benchmarkFastAND(b, 1024) }
|
||||||
|
func BenchmarkFastAND2KB(b *testing.B) { benchmarkFastAND(b, 2048) }
|
||||||
|
func BenchmarkFastAND4KB(b *testing.B) { benchmarkFastAND(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkFastAND(b *testing.B, size int) {
|
||||||
|
p, q := make([]byte, size), make([]byte, size)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ANDBytes(p, p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the baseline AND performance.
|
||||||
|
func BenchmarkBaseAND1KB(b *testing.B) { benchmarkBaseAND(b, 1024) }
|
||||||
|
func BenchmarkBaseAND2KB(b *testing.B) { benchmarkBaseAND(b, 2048) }
|
||||||
|
func BenchmarkBaseAND4KB(b *testing.B) { benchmarkBaseAND(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkBaseAND(b *testing.B, size int) {
|
||||||
|
p, q := make([]byte, size), make([]byte, size)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
safeANDBytes(p, p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the potentially optimized OR performance.
|
||||||
|
func BenchmarkFastOR1KB(b *testing.B) { benchmarkFastOR(b, 1024) }
|
||||||
|
func BenchmarkFastOR2KB(b *testing.B) { benchmarkFastOR(b, 2048) }
|
||||||
|
func BenchmarkFastOR4KB(b *testing.B) { benchmarkFastOR(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkFastOR(b *testing.B, size int) {
|
||||||
|
p, q := make([]byte, size), make([]byte, size)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ORBytes(p, p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the baseline OR performance.
|
||||||
|
func BenchmarkBaseOR1KB(b *testing.B) { benchmarkBaseOR(b, 1024) }
|
||||||
|
func BenchmarkBaseOR2KB(b *testing.B) { benchmarkBaseOR(b, 2048) }
|
||||||
|
func BenchmarkBaseOR4KB(b *testing.B) { benchmarkBaseOR(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkBaseOR(b *testing.B, size int) {
|
||||||
|
p, q := make([]byte, size), make([]byte, size)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
safeORBytes(p, p, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the potentially optimized bit testing performance.
|
||||||
|
func BenchmarkFastTest1KB(b *testing.B) { benchmarkFastTest(b, 1024) }
|
||||||
|
func BenchmarkFastTest2KB(b *testing.B) { benchmarkFastTest(b, 2048) }
|
||||||
|
func BenchmarkFastTest4KB(b *testing.B) { benchmarkFastTest(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkFastTest(b *testing.B, size int) {
|
||||||
|
p := make([]byte, size)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
TestBytes(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmarks the baseline bit testing performance.
|
||||||
|
func BenchmarkBaseTest1KB(b *testing.B) { benchmarkBaseTest(b, 1024) }
|
||||||
|
func BenchmarkBaseTest2KB(b *testing.B) { benchmarkBaseTest(b, 2048) }
|
||||||
|
func BenchmarkBaseTest4KB(b *testing.B) { benchmarkBaseTest(b, 4096) }
|
||||||
|
|
||||||
|
func benchmarkBaseTest(b *testing.B, size int) {
|
||||||
|
p := make([]byte, size)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
safeTestBytes(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
170
common/bitutil/compress.go
Normal file
170
common/bitutil/compress.go
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
// 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 bitutil
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// errMissingData is returned from decompression if the byte referenced by
|
||||||
|
// the bitset header overflows the input data.
|
||||||
|
errMissingData = errors.New("missing bytes on input")
|
||||||
|
|
||||||
|
// errUnreferencedData is returned from decompression if not all bytes were used
|
||||||
|
// up from the input data after decompressing it.
|
||||||
|
errUnreferencedData = errors.New("extra bytes on input")
|
||||||
|
|
||||||
|
// errExceededTarget is returned from decompression if the bitset header has
|
||||||
|
// more bits defined than the number of target buffer space available.
|
||||||
|
errExceededTarget = errors.New("target data size exceeded")
|
||||||
|
|
||||||
|
// errZeroContent is returned from decompression if a data byte referenced in
|
||||||
|
// the bitset header is actually a zero byte.
|
||||||
|
errZeroContent = errors.New("zero byte in input content")
|
||||||
|
)
|
||||||
|
|
||||||
|
// The compression algorithm implemented by CompressBytes and DecompressBytes is
|
||||||
|
// optimized for sparse input data which contains a lot of zero bytes. Decompression
|
||||||
|
// requires knowledge of the decompressed data length.
|
||||||
|
//
|
||||||
|
// Compression works as follows:
|
||||||
|
//
|
||||||
|
// if data only contains zeroes,
|
||||||
|
// CompressBytes(data) == nil
|
||||||
|
// otherwise if len(data) <= 1,
|
||||||
|
// CompressBytes(data) == data
|
||||||
|
// otherwise:
|
||||||
|
// CompressBytes(data) == append(CompressBytes(nonZeroBitset(data)), nonZeroBytes(data)...)
|
||||||
|
// where
|
||||||
|
// nonZeroBitset(data) is a bit vector with len(data) bits (MSB first):
|
||||||
|
// nonZeroBitset(data)[i/8] && (1 << (7-i%8)) != 0 if data[i] != 0
|
||||||
|
// len(nonZeroBitset(data)) == (len(data)+7)/8
|
||||||
|
// nonZeroBytes(data) contains the non-zero bytes of data in the same order
|
||||||
|
|
||||||
|
// CompressBytes compresses the input byte slice according to the sparse bitset
|
||||||
|
// representation algorithm. If the result is bigger than the original input, no
|
||||||
|
// compression is done.
|
||||||
|
func CompressBytes(data []byte) []byte {
|
||||||
|
if out := bitsetEncodeBytes(data); len(out) < len(data) {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
cpy := make([]byte, len(data))
|
||||||
|
copy(cpy, data)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitsetEncodeBytes compresses the input byte slice according to the sparse
|
||||||
|
// bitset representation algorithm.
|
||||||
|
func bitsetEncodeBytes(data []byte) []byte {
|
||||||
|
// Empty slices get compressed to nil
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// One byte slices compress to nil or retain the single byte
|
||||||
|
if len(data) == 1 {
|
||||||
|
if data[0] == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
// Calculate the bitset of set bytes, and gather the non-zero bytes
|
||||||
|
nonZeroBitset := make([]byte, (len(data)+7)/8)
|
||||||
|
nonZeroBytes := make([]byte, 0, len(data))
|
||||||
|
|
||||||
|
for i, b := range data {
|
||||||
|
if b != 0 {
|
||||||
|
nonZeroBytes = append(nonZeroBytes, b)
|
||||||
|
nonZeroBitset[i/8] |= 1 << byte(7-i%8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nonZeroBytes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return append(bitsetEncodeBytes(nonZeroBitset), nonZeroBytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecompressBytes decompresses data with a known target size. If the input data
|
||||||
|
// matches the size of the target, it means no compression was done in the first
|
||||||
|
// place.
|
||||||
|
func DecompressBytes(data []byte, target int) ([]byte, error) {
|
||||||
|
if len(data) > target {
|
||||||
|
return nil, errExceededTarget
|
||||||
|
}
|
||||||
|
if len(data) == target {
|
||||||
|
cpy := make([]byte, len(data))
|
||||||
|
copy(cpy, data)
|
||||||
|
return cpy, nil
|
||||||
|
}
|
||||||
|
return bitsetDecodeBytes(data, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitsetDecodeBytes decompresses data with a known target size.
|
||||||
|
func bitsetDecodeBytes(data []byte, target int) ([]byte, error) {
|
||||||
|
out, size, err := bitsetDecodePartialBytes(data, target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if size != len(data) {
|
||||||
|
return nil, errUnreferencedData
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitsetDecodePartialBytes decompresses data with a known target size, but does
|
||||||
|
// not enforce consuming all the input bytes. In addition to the decompressed
|
||||||
|
// output, the function returns the length of compressed input data corresponding
|
||||||
|
// to the output as the input slice may be longer.
|
||||||
|
func bitsetDecodePartialBytes(data []byte, target int) ([]byte, int, error) {
|
||||||
|
// Sanity check 0 targets to avoid infinite recursion
|
||||||
|
if target == 0 {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
// Handle the zero and single byte corner cases
|
||||||
|
decomp := make([]byte, target)
|
||||||
|
if len(data) == 0 {
|
||||||
|
return decomp, 0, nil
|
||||||
|
}
|
||||||
|
if target == 1 {
|
||||||
|
decomp[0] = data[0] // copy to avoid referencing the input slice
|
||||||
|
if data[0] != 0 {
|
||||||
|
return decomp, 1, nil
|
||||||
|
}
|
||||||
|
return decomp, 0, nil
|
||||||
|
}
|
||||||
|
// Decompress the bitset of set bytes and distribute the non zero bytes
|
||||||
|
nonZeroBitset, ptr, err := bitsetDecodePartialBytes(data, (target+7)/8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ptr, err
|
||||||
|
}
|
||||||
|
for i := 0; i < 8*len(nonZeroBitset); i++ {
|
||||||
|
if nonZeroBitset[i/8]&(1<<byte(7-i%8)) != 0 {
|
||||||
|
// Make sure we have enough data to push into the correct slot
|
||||||
|
if ptr >= len(data) {
|
||||||
|
return nil, 0, errMissingData
|
||||||
|
}
|
||||||
|
if i >= len(decomp) {
|
||||||
|
return nil, 0, errExceededTarget
|
||||||
|
}
|
||||||
|
// Make sure the data is valid and push into the slot
|
||||||
|
if data[ptr] == 0 {
|
||||||
|
return nil, 0, errZeroContent
|
||||||
|
}
|
||||||
|
decomp[i] = data[ptr]
|
||||||
|
ptr++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decomp, ptr, nil
|
||||||
|
}
|
||||||
56
common/bitutil/compress_fuzz.go
Normal file
56
common/bitutil/compress_fuzz.go
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package bitutil
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
// Fuzz implements a go-fuzz fuzzer method to test various encoding method
|
||||||
|
// invocations.
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if data[0]%2 == 0 {
|
||||||
|
return fuzzEncode(data[1:])
|
||||||
|
}
|
||||||
|
return fuzzDecode(data[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and
|
||||||
|
// decoding algorithm.
|
||||||
|
func fuzzEncode(data []byte) int {
|
||||||
|
proc, _ := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
|
||||||
|
if !bytes.Equal(data, proc) {
|
||||||
|
panic("content mismatch")
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and
|
||||||
|
// reencoding algorithm.
|
||||||
|
func fuzzDecode(data []byte) int {
|
||||||
|
blob, err := bitsetDecodeBytes(data, 1024)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if comp := bitsetEncodeBytes(blob); !bytes.Equal(comp, data) {
|
||||||
|
panic("content mismatch")
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
181
common/bitutil/compress_test.go
Normal file
181
common/bitutil/compress_test.go
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
// 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 bitutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that data bitset encoding and decoding works and is bijective.
|
||||||
|
func TestEncodingCycle(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
// Tests generated by go-fuzz to maximize code coverage
|
||||||
|
"0x000000000000000000",
|
||||||
|
"0xef0400",
|
||||||
|
"0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb",
|
||||||
|
"0x7b64000000",
|
||||||
|
"0x000034000000000000",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f0000000000000000000",
|
||||||
|
"0x4912385c0e7b64000000",
|
||||||
|
"0x000034000000000000000000000000000000",
|
||||||
|
"0x00",
|
||||||
|
"0x000003e834ff7f0000",
|
||||||
|
"0x0000",
|
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000ff00",
|
||||||
|
"0x895f0c6a020f850c6a020f85f88df88d",
|
||||||
|
"0xdf7070533534333636313639343638373432313536346c1bc3315aac2f65fefb",
|
||||||
|
"0x0000000000",
|
||||||
|
"0xdf70706336346c65fefb",
|
||||||
|
"0x00006d643634000000",
|
||||||
|
"0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe",
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
data := hexutil.MustDecode(tt)
|
||||||
|
|
||||||
|
proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d: failed to decompress compressed data: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !bytes.Equal(data, proc) {
|
||||||
|
t.Errorf("test %d: compress/decompress mismatch: have %x, want %x", i, proc, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that data bitset decoding and rencoding works and is bijective.
|
||||||
|
func TestDecodingCycle(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
size int
|
||||||
|
input string
|
||||||
|
fail error
|
||||||
|
}{
|
||||||
|
{size: 0, input: "0x"},
|
||||||
|
|
||||||
|
// Crashers generated by go-fuzz
|
||||||
|
{size: 0, input: "0x0020", fail: errUnreferencedData},
|
||||||
|
{size: 0, input: "0x30", fail: errUnreferencedData},
|
||||||
|
{size: 1, input: "0x00", fail: errUnreferencedData},
|
||||||
|
{size: 2, input: "0x07", fail: errMissingData},
|
||||||
|
{size: 1024, input: "0x8000", fail: errZeroContent},
|
||||||
|
|
||||||
|
// Tests generated by go-fuzz to maximize code coverage
|
||||||
|
{size: 29490, input: "0x343137343733323134333839373334323073333930783e3078333930783e70706336346c65303e", fail: errMissingData},
|
||||||
|
{size: 59395, input: "0x00", fail: errUnreferencedData},
|
||||||
|
{size: 52574, input: "0x70706336346c65c0de", fail: errExceededTarget},
|
||||||
|
{size: 42264, input: "0x07", fail: errMissingData},
|
||||||
|
{size: 52, input: "0xa5045bad48f4", fail: errExceededTarget},
|
||||||
|
{size: 52574, input: "0xc0de", fail: errMissingData},
|
||||||
|
{size: 52574, input: "0x"},
|
||||||
|
{size: 29490, input: "0x34313734373332313433383937333432307333393078073034333839373334323073333930783e3078333937333432307333393078073061333930783e70706336346c65303e", fail: errMissingData},
|
||||||
|
{size: 29491, input: "0x3973333930783e30783e", fail: errMissingData},
|
||||||
|
|
||||||
|
{size: 1024, input: "0x808080608080"},
|
||||||
|
{size: 1024, input: "0x808470705e3632383337363033313434303137393130306c6580ef46806380635a80"},
|
||||||
|
{size: 1024, input: "0x8080808070"},
|
||||||
|
{size: 1024, input: "0x808070705e36346c6580ef46806380635a80"},
|
||||||
|
{size: 1024, input: "0x80808046802680"},
|
||||||
|
{size: 1024, input: "0x4040404035"},
|
||||||
|
{size: 1024, input: "0x4040bf3ba2b3f684402d353234373438373934409fe5b1e7ada94ebfd7d0505e27be4035"},
|
||||||
|
{size: 1024, input: "0x404040bf3ba2b3f6844035"},
|
||||||
|
{size: 1024, input: "0x40402d35323437343837393440bfd7d0505e27be4035"},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
data := hexutil.MustDecode(tt.input)
|
||||||
|
|
||||||
|
orig, err := bitsetDecodeBytes(data, tt.size)
|
||||||
|
if err != tt.fail {
|
||||||
|
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.fail)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if comp := bitsetEncodeBytes(orig); !bytes.Equal(comp, data) {
|
||||||
|
t.Errorf("test %d: decompress/compress mismatch: have %x, want %x", i, comp, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCompression tests that compression works by returning either the bitset
|
||||||
|
// encoded input, or the actual input if the bitset version is longer.
|
||||||
|
func TestCompression(t *testing.T) {
|
||||||
|
// Check the the compression returns the bitset encoding is shorter
|
||||||
|
in := hexutil.MustDecode("0x4912385c0e7b64000000")
|
||||||
|
out := hexutil.MustDecode("0x80fe4912385c0e7b64")
|
||||||
|
|
||||||
|
if data := CompressBytes(in); bytes.Compare(data, out) != 0 {
|
||||||
|
t.Errorf("encoding mismatch for sparse data: have %x, want %x", data, out)
|
||||||
|
}
|
||||||
|
if data, err := DecompressBytes(out, len(in)); err != nil || bytes.Compare(data, in) != 0 {
|
||||||
|
t.Errorf("decoding mismatch for sparse data: have %x, want %x, error %v", data, in, err)
|
||||||
|
}
|
||||||
|
// Check the the compression returns the input if the bitset encoding is longer
|
||||||
|
in = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb")
|
||||||
|
out = hexutil.MustDecode("0xdf7070533534333636313639343638373532313536346c1bc33339343837313070706336343035336336346c65fefb3930393233383838ac2f65fefb")
|
||||||
|
|
||||||
|
if data := CompressBytes(in); bytes.Compare(data, out) != 0 {
|
||||||
|
t.Errorf("encoding mismatch for dense data: have %x, want %x", data, out)
|
||||||
|
}
|
||||||
|
if data, err := DecompressBytes(out, len(in)); err != nil || bytes.Compare(data, in) != 0 {
|
||||||
|
t.Errorf("decoding mismatch for dense data: have %x, want %x, error %v", data, in, err)
|
||||||
|
}
|
||||||
|
// Check that decompressing a longer input than the target fails
|
||||||
|
if _, err := DecompressBytes([]byte{0xc0, 0x01, 0x01}, 2); err != errExceededTarget {
|
||||||
|
t.Errorf("decoding error mismatch for long data: have %v, want %v", err, errExceededTarget)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crude benchmark for compressing random slices of bytes.
|
||||||
|
func BenchmarkEncoding1KBVerySparse(b *testing.B) { benchmarkEncoding(b, 1024, 0.0001) }
|
||||||
|
func BenchmarkEncoding2KBVerySparse(b *testing.B) { benchmarkEncoding(b, 2048, 0.0001) }
|
||||||
|
func BenchmarkEncoding4KBVerySparse(b *testing.B) { benchmarkEncoding(b, 4096, 0.0001) }
|
||||||
|
|
||||||
|
func BenchmarkEncoding1KBSparse(b *testing.B) { benchmarkEncoding(b, 1024, 0.001) }
|
||||||
|
func BenchmarkEncoding2KBSparse(b *testing.B) { benchmarkEncoding(b, 2048, 0.001) }
|
||||||
|
func BenchmarkEncoding4KBSparse(b *testing.B) { benchmarkEncoding(b, 4096, 0.001) }
|
||||||
|
|
||||||
|
func BenchmarkEncoding1KBDense(b *testing.B) { benchmarkEncoding(b, 1024, 0.1) }
|
||||||
|
func BenchmarkEncoding2KBDense(b *testing.B) { benchmarkEncoding(b, 2048, 0.1) }
|
||||||
|
func BenchmarkEncoding4KBDense(b *testing.B) { benchmarkEncoding(b, 4096, 0.1) }
|
||||||
|
|
||||||
|
func BenchmarkEncoding1KBSaturated(b *testing.B) { benchmarkEncoding(b, 1024, 0.5) }
|
||||||
|
func BenchmarkEncoding2KBSaturated(b *testing.B) { benchmarkEncoding(b, 2048, 0.5) }
|
||||||
|
func BenchmarkEncoding4KBSaturated(b *testing.B) { benchmarkEncoding(b, 4096, 0.5) }
|
||||||
|
|
||||||
|
func benchmarkEncoding(b *testing.B, bytes int, fill float64) {
|
||||||
|
// Generate a random slice of bytes to compress
|
||||||
|
random := rand.NewSource(0) // reproducible and comparable
|
||||||
|
|
||||||
|
data := make([]byte, bytes)
|
||||||
|
bits := int(float64(bytes) * 8 * fill)
|
||||||
|
|
||||||
|
for i := 0; i < bits; i++ {
|
||||||
|
idx := random.Int63() % int64(len(data))
|
||||||
|
bit := uint(random.Int63() % 8)
|
||||||
|
data[idx] |= 1 << bit
|
||||||
|
}
|
||||||
|
// Reset the benchmark and measure encoding/decoding
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
bitsetDecodeBytes(bitsetEncodeBytes(data), len(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -89,18 +89,18 @@ func Hex2BytesFixed(str string, flen int) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RightPadBytes(slice []byte, l int) []byte {
|
func RightPadBytes(slice []byte, l int) []byte {
|
||||||
if l < len(slice) {
|
if l <= len(slice) {
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
padded := make([]byte, l)
|
padded := make([]byte, l)
|
||||||
copy(padded[0:len(slice)], slice)
|
copy(padded, slice)
|
||||||
|
|
||||||
return padded
|
return padded
|
||||||
}
|
}
|
||||||
|
|
||||||
func LeftPadBytes(slice []byte, l int) []byte {
|
func LeftPadBytes(slice []byte, l int) []byte {
|
||||||
if l < len(slice) {
|
if l <= len(slice) {
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,11 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`)
|
||||||
versionRegexp = regexp.MustCompile(`[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
solcParams = []string{
|
|
||||||
"--combined-json", "bin,abi,userdoc,devdoc",
|
|
||||||
"--add-std", // include standard lib contracts
|
|
||||||
"--optimize", // code optimizer switched on
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Contract struct {
|
type Contract struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
|
|
@ -54,19 +45,35 @@ type ContractInfo struct {
|
||||||
AbiDefinition interface{} `json:"abiDefinition"`
|
AbiDefinition interface{} `json:"abiDefinition"`
|
||||||
UserDoc interface{} `json:"userDoc"`
|
UserDoc interface{} `json:"userDoc"`
|
||||||
DeveloperDoc interface{} `json:"developerDoc"`
|
DeveloperDoc interface{} `json:"developerDoc"`
|
||||||
|
Metadata string `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solidity contains information about the solidity compiler.
|
// Solidity contains information about the solidity compiler.
|
||||||
type Solidity struct {
|
type Solidity struct {
|
||||||
Path, Version, FullVersion string
|
Path, Version, FullVersion string
|
||||||
|
Major, Minor, Patch int
|
||||||
}
|
}
|
||||||
|
|
||||||
// --combined-output format
|
// --combined-output format
|
||||||
type solcOutput struct {
|
type solcOutput struct {
|
||||||
Contracts map[string]struct{ Bin, Abi, Devdoc, Userdoc string }
|
Contracts map[string]struct {
|
||||||
|
Bin, Abi, Devdoc, Userdoc, Metadata string
|
||||||
|
}
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Solidity) makeArgs() []string {
|
||||||
|
p := []string{
|
||||||
|
"--combined-json", "bin,abi,userdoc,devdoc",
|
||||||
|
"--add-std", // include standard lib contracts
|
||||||
|
"--optimize", // code optimizer switched on
|
||||||
|
}
|
||||||
|
if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
|
||||||
|
p[1] += ",metadata"
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
// SolidityVersion runs solc and parses its version output.
|
// SolidityVersion runs solc and parses its version output.
|
||||||
func SolidityVersion(solc string) (*Solidity, error) {
|
func SolidityVersion(solc string) (*Solidity, error) {
|
||||||
if solc == "" {
|
if solc == "" {
|
||||||
|
|
@ -75,13 +82,23 @@ func SolidityVersion(solc string) (*Solidity, error) {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
cmd := exec.Command(solc, "--version")
|
cmd := exec.Command(solc, "--version")
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
if err := cmd.Run(); err != nil {
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s := &Solidity{
|
matches := versionRegexp.FindStringSubmatch(out.String())
|
||||||
Path: cmd.Path,
|
if len(matches) != 4 {
|
||||||
FullVersion: out.String(),
|
return nil, fmt.Errorf("can't parse solc version %q", out.String())
|
||||||
Version: versionRegexp.FindString(out.String()),
|
}
|
||||||
|
s := &Solidity{Path: cmd.Path, FullVersion: out.String(), Version: matches[0]}
|
||||||
|
if s.Major, err = strconv.Atoi(matches[1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.Minor, err = strconv.Atoi(matches[2]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s.Patch, err = strconv.Atoi(matches[3]); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
@ -91,13 +108,14 @@ func CompileSolidityString(solc, source string) (map[string]*Contract, error) {
|
||||||
if len(source) == 0 {
|
if len(source) == 0 {
|
||||||
return nil, errors.New("solc: empty source string")
|
return nil, errors.New("solc: empty source string")
|
||||||
}
|
}
|
||||||
if solc == "" {
|
s, err := SolidityVersion(solc)
|
||||||
solc = "solc"
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
args := append(solcParams, "--")
|
args := append(s.makeArgs(), "--")
|
||||||
cmd := exec.Command(solc, append(args, "-")...)
|
cmd := exec.Command(s.Path, append(args, "-")...)
|
||||||
cmd.Stdin = strings.NewReader(source)
|
cmd.Stdin = strings.NewReader(source)
|
||||||
return runsolc(cmd, source)
|
return s.run(cmd, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompileSolidity compiles all given Solidity source files.
|
// CompileSolidity compiles all given Solidity source files.
|
||||||
|
|
@ -109,15 +127,16 @@ func CompileSolidity(solc string, sourcefiles ...string) (map[string]*Contract,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if solc == "" {
|
s, err := SolidityVersion(solc)
|
||||||
solc = "solc"
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
args := append(solcParams, "--")
|
args := append(s.makeArgs(), "--")
|
||||||
cmd := exec.Command(solc, append(args, sourcefiles...)...)
|
cmd := exec.Command(s.Path, append(args, sourcefiles...)...)
|
||||||
return runsolc(cmd, source)
|
return s.run(cmd, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runsolc(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
|
func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
|
||||||
var stderr, stdout bytes.Buffer
|
var stderr, stdout bytes.Buffer
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
|
|
@ -128,7 +147,6 @@ func runsolc(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
|
||||||
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
|
if err := json.Unmarshal(stdout.Bytes(), &output); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
shortVersion := versionRegexp.FindString(output.Version)
|
|
||||||
|
|
||||||
// Compilation succeeded, assemble and return the contracts.
|
// Compilation succeeded, assemble and return the contracts.
|
||||||
contracts := make(map[string]*Contract)
|
contracts := make(map[string]*Contract)
|
||||||
|
|
@ -151,12 +169,13 @@ func runsolc(cmd *exec.Cmd, source string) (map[string]*Contract, error) {
|
||||||
Info: ContractInfo{
|
Info: ContractInfo{
|
||||||
Source: source,
|
Source: source,
|
||||||
Language: "Solidity",
|
Language: "Solidity",
|
||||||
LanguageVersion: shortVersion,
|
LanguageVersion: s.Version,
|
||||||
CompilerVersion: shortVersion,
|
CompilerVersion: s.Version,
|
||||||
CompilerOptions: strings.Join(solcParams, " "),
|
CompilerOptions: strings.Join(s.makeArgs(), " "),
|
||||||
AbiDefinition: abi,
|
AbiDefinition: abi,
|
||||||
UserDoc: userdoc,
|
UserDoc: userdoc,
|
||||||
DeveloperDoc: devdoc,
|
DeveloperDoc: devdoc,
|
||||||
|
Metadata: info.Metadata,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,13 +193,3 @@ func slurpFiles(files []string) (string, error) {
|
||||||
}
|
}
|
||||||
return concat.String(), nil
|
return concat.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveInfo serializes info to the given file and returns its Keccak256 hash.
|
|
||||||
func SaveInfo(info *ContractInfo, filename string) (common.Hash, error) {
|
|
||||||
infojson, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
|
||||||
contenthash := common.BytesToHash(crypto.Keccak256(infojson))
|
|
||||||
return contenthash, ioutil.WriteFile(filename, infojson, 0600)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,8 @@
|
||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -36,7 +30,6 @@ contract test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
testInfo = `{"source":"\ncontract test {\n /// @notice Will multiply ` + "`a`" + ` by 7.\n function multiply(uint a) returns(uint d) {\n return a * 7;\n }\n}\n","language":"Solidity","languageVersion":"0.1.1","compilerVersion":"0.1.1","compilerOptions":"--binary file --json-abi file --add-std 1","abiDefinition":[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}],"userDoc":{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}},"developerDoc":{"methods":{}}}`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func skipWithoutSolc(t *testing.T) {
|
func skipWithoutSolc(t *testing.T) {
|
||||||
|
|
@ -56,9 +49,12 @@ func TestCompiler(t *testing.T) {
|
||||||
t.Errorf("one contract expected, got %d", len(contracts))
|
t.Errorf("one contract expected, got %d", len(contracts))
|
||||||
}
|
}
|
||||||
c, ok := contracts["test"]
|
c, ok := contracts["test"]
|
||||||
|
if !ok {
|
||||||
|
c, ok = contracts["<stdin>:test"]
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatal("info for contract 'test' not present in result")
|
t.Fatal("info for contract 'test' not present in result")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if c.Code == "" {
|
if c.Code == "" {
|
||||||
t.Error("empty code")
|
t.Error("empty code")
|
||||||
}
|
}
|
||||||
|
|
@ -79,28 +75,3 @@ func TestCompileError(t *testing.T) {
|
||||||
}
|
}
|
||||||
t.Logf("error: %v", err)
|
t.Logf("error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSaveInfo(t *testing.T) {
|
|
||||||
var cinfo ContractInfo
|
|
||||||
err := json.Unmarshal([]byte(testInfo), &cinfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%v", err)
|
|
||||||
}
|
|
||||||
filename := path.Join(os.TempDir(), "solctest.info.json")
|
|
||||||
os.Remove(filename)
|
|
||||||
cinfohash, err := SaveInfo(&cinfo, filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error extracting info: %v", err)
|
|
||||||
}
|
|
||||||
got, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("error reading '%v': %v", filename, err)
|
|
||||||
}
|
|
||||||
if string(got) != testInfo {
|
|
||||||
t.Errorf("incorrect info.json extracted, expected:\n%s\ngot\n%s", testInfo, string(got))
|
|
||||||
}
|
|
||||||
wantHash := common.HexToHash("0x22450a77f0c3ff7a395948d07bc1456881226a1b6325f4189cb5f1254a824080")
|
|
||||||
if cinfohash != wantHash {
|
|
||||||
t.Errorf("content hash for info is incorrect. expected %v, got %v", wantHash.Hex(), cinfohash.Hex())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ package hexutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -41,17 +40,23 @@ import (
|
||||||
const uintBits = 32 << (uint64(^uint(0)) >> 63)
|
const uintBits = 32 << (uint64(^uint(0)) >> 63)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrEmptyString = errors.New("empty hex string")
|
ErrEmptyString = &decError{"empty hex string"}
|
||||||
ErrMissingPrefix = errors.New("missing 0x prefix for hex data")
|
ErrSyntax = &decError{"invalid hex string"}
|
||||||
ErrSyntax = errors.New("invalid hex")
|
ErrMissingPrefix = &decError{"hex string without 0x prefix"}
|
||||||
ErrEmptyNumber = errors.New("hex number has no digits after 0x")
|
ErrOddLength = &decError{"hex string of odd length"}
|
||||||
ErrLeadingZero = errors.New("hex number has leading zero digits after 0x")
|
ErrEmptyNumber = &decError{"hex string \"0x\""}
|
||||||
ErrOddLength = errors.New("hex string has odd length")
|
ErrLeadingZero = &decError{"hex number with leading zero digits"}
|
||||||
ErrUint64Range = errors.New("hex number does not fit into 64 bits")
|
ErrUint64Range = &decError{"hex number > 64 bits"}
|
||||||
ErrUintRange = fmt.Errorf("hex number does not fit into %d bits", uintBits)
|
ErrUintRange = &decError{fmt.Sprintf("hex number > %d bits", uintBits)}
|
||||||
ErrBig256Range = errors.New("hex number does not fit into 256 bits")
|
ErrBig256Range = &decError{"hex number > 256 bits"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type decError struct{ msg string }
|
||||||
|
|
||||||
|
func (err decError) Error() string {
|
||||||
|
return string(err.msg)
|
||||||
|
}
|
||||||
|
|
||||||
// Decode decodes a hex string with 0x prefix.
|
// Decode decodes a hex string with 0x prefix.
|
||||||
func Decode(input string) ([]byte, error) {
|
func Decode(input string) ([]byte, error) {
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,19 @@ package hexutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
textZero = []byte(`0x0`)
|
textZero = []byte(`0x0`)
|
||||||
errNonString = errors.New("cannot unmarshal non-string as hex data")
|
bytesT = reflect.TypeOf(Bytes(nil))
|
||||||
|
bigT = reflect.TypeOf((*Big)(nil))
|
||||||
|
uintT = reflect.TypeOf(Uint(0))
|
||||||
|
uint64T = reflect.TypeOf(Uint64(0))
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
|
// Bytes marshals/unmarshals as a JSON string with 0x prefix.
|
||||||
|
|
@ -44,9 +48,9 @@ func (b Bytes) MarshalText() ([]byte, error) {
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
func (b *Bytes) UnmarshalJSON(input []byte) error {
|
func (b *Bytes) UnmarshalJSON(input []byte) error {
|
||||||
if !isString(input) {
|
if !isString(input) {
|
||||||
return errNonString
|
return errNonString(bytesT)
|
||||||
}
|
}
|
||||||
return b.UnmarshalText(input[1 : len(input)-1])
|
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bytesT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
|
@ -69,6 +73,16 @@ func (b Bytes) String() string {
|
||||||
return Encode(b)
|
return Encode(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalFixedJSON decodes the input as a string with 0x prefix. The length of out
|
||||||
|
// determines the required input length. This function is commonly used to implement the
|
||||||
|
// UnmarshalJSON method for fixed-size types.
|
||||||
|
func UnmarshalFixedJSON(typ reflect.Type, input, out []byte) error {
|
||||||
|
if !isString(input) {
|
||||||
|
return errNonString(typ)
|
||||||
|
}
|
||||||
|
return wrapTypeError(UnmarshalFixedText(typ.String(), input[1:len(input)-1], out), typ)
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out
|
// UnmarshalFixedText decodes the input as a string with 0x prefix. The length of out
|
||||||
// determines the required input length. This function is commonly used to implement the
|
// determines the required input length. This function is commonly used to implement the
|
||||||
// UnmarshalText method for fixed-size types.
|
// UnmarshalText method for fixed-size types.
|
||||||
|
|
@ -127,9 +141,9 @@ func (b Big) MarshalText() ([]byte, error) {
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
func (b *Big) UnmarshalJSON(input []byte) error {
|
func (b *Big) UnmarshalJSON(input []byte) error {
|
||||||
if !isString(input) {
|
if !isString(input) {
|
||||||
return errNonString
|
return errNonString(bigT)
|
||||||
}
|
}
|
||||||
return b.UnmarshalText(input[1 : len(input)-1])
|
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), bigT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler
|
// UnmarshalText implements encoding.TextUnmarshaler
|
||||||
|
|
@ -189,9 +203,9 @@ func (b Uint64) MarshalText() ([]byte, error) {
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
func (b *Uint64) UnmarshalJSON(input []byte) error {
|
func (b *Uint64) UnmarshalJSON(input []byte) error {
|
||||||
if !isString(input) {
|
if !isString(input) {
|
||||||
return errNonString
|
return errNonString(uint64T)
|
||||||
}
|
}
|
||||||
return b.UnmarshalText(input[1 : len(input)-1])
|
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uint64T)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler
|
// UnmarshalText implements encoding.TextUnmarshaler
|
||||||
|
|
@ -233,9 +247,9 @@ func (b Uint) MarshalText() ([]byte, error) {
|
||||||
// UnmarshalJSON implements json.Unmarshaler.
|
// UnmarshalJSON implements json.Unmarshaler.
|
||||||
func (b *Uint) UnmarshalJSON(input []byte) error {
|
func (b *Uint) UnmarshalJSON(input []byte) error {
|
||||||
if !isString(input) {
|
if !isString(input) {
|
||||||
return errNonString
|
return errNonString(uintT)
|
||||||
}
|
}
|
||||||
return b.UnmarshalText(input[1 : len(input)-1])
|
return wrapTypeError(b.UnmarshalText(input[1:len(input)-1]), uintT)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
|
@ -295,3 +309,14 @@ func checkNumberText(input []byte) (raw []byte, err error) {
|
||||||
}
|
}
|
||||||
return input, nil
|
return input, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wrapTypeError(err error, typ reflect.Type) error {
|
||||||
|
if _, ok := err.(*decError); ok {
|
||||||
|
return &json.UnmarshalTypeError{Value: err.Error(), Type: typ}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func errNonString(typ reflect.Type) error {
|
||||||
|
return &json.UnmarshalTypeError{Value: "non-string", Type: typ}
|
||||||
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue