mirror of
https://github.com/arnaucube/go-ethereum.git
synced 2026-03-03 15:44:52 +01:00
Compare commits
241 Commits
release/1.
...
v1.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f54075760 | ||
|
|
57bca0af8c | ||
|
|
a5c0bbb4f4 | ||
|
|
0544a43c13 | ||
|
|
20797348ca | ||
|
|
88f2839da4 | ||
|
|
b007412db1 | ||
|
|
da41a7258d | ||
|
|
8d32c4b990 | ||
|
|
12dab53495 | ||
|
|
70fbc87379 | ||
|
|
6c6247a690 | ||
|
|
589b603a9b | ||
|
|
9123eceb0f | ||
|
|
1d39912a9b | ||
|
|
69c1f2c2a7 | ||
|
|
52ad848b2e | ||
|
|
4065695350 | ||
|
|
969474f60a | ||
|
|
62ffec1be3 | ||
|
|
57fd2da0fe | ||
|
|
aa9432b816 | ||
|
|
7a0019c63b | ||
|
|
96dad6b6f6 | ||
|
|
5cf75a30c1 | ||
|
|
2f849ade82 | ||
|
|
a00f4a12a9 | ||
|
|
42628ba7ed | ||
|
|
ccf8083537 | ||
|
|
c4712bf96b | ||
|
|
2b4c7e9b37 | ||
|
|
03daf601c1 | ||
|
|
eb07dbb079 | ||
|
|
1a4e68721a | ||
|
|
806430a252 | ||
|
|
55599ee95d | ||
|
|
59336283c0 | ||
|
|
203440e813 | ||
|
|
c3f238dd53 | ||
|
|
bc0666fb27 | ||
|
|
0662384d29 | ||
|
|
b4e05adcc7 | ||
|
|
efc9209158 | ||
|
|
ec28a58cc1 | ||
|
|
4dedde7beb | ||
|
|
fdb34b7a7c | ||
|
|
07d4a02257 | ||
|
|
3e89b80ccb | ||
|
|
017b9f7eac | ||
|
|
566d5c0777 | ||
|
|
6198c53e28 | ||
|
|
a9e4a90d57 | ||
|
|
59a852e418 | ||
|
|
dd7a715d73 | ||
|
|
722bac84fa | ||
|
|
23bca0f374 | ||
|
|
367c329b88 | ||
|
|
2ef3815af4 | ||
|
|
4dd0727c39 | ||
|
|
8f6990dc7d | ||
|
|
c335821479 | ||
|
|
952482d5e4 | ||
|
|
5c83a4e5dd | ||
|
|
1bf508b449 | ||
|
|
05ade19302 | ||
|
|
ec96216d16 | ||
|
|
397c6cde1e | ||
|
|
302c17c36a | ||
|
|
924065e19d | ||
|
|
48641d7308 | ||
|
|
5d4267911a | ||
|
|
92580d69d3 | ||
|
|
84be009154 | ||
|
|
02aeb3d766 | ||
|
|
370dca4491 | ||
|
|
f08cd94fb7 | ||
|
|
216e584899 | ||
|
|
18a7d31338 | ||
|
|
938cf4528a | ||
|
|
81ad8f665d | ||
|
|
90e5744d6f | ||
|
|
3f40b22dac | ||
|
|
fd869dc839 | ||
|
|
bd0dbfa2a8 | ||
|
|
56152b31ac | ||
|
|
023769d9d4 | ||
|
|
b06e20bc7c | ||
|
|
3a5a5599dd | ||
|
|
83d1657444 | ||
|
|
9d06026c19 | ||
|
|
5c2f1e0014 | ||
|
|
a139041d40 | ||
|
|
1c2378b926 | ||
|
|
ae71da1b03 | ||
|
|
7a59a9380e | ||
|
|
762f3a48a0 | ||
|
|
b47285f1cf | ||
|
|
6f69cdd109 | ||
|
|
b8caba9709 | ||
|
|
9d48dbf5c2 | ||
|
|
85a1eda59e | ||
|
|
72e70bcec2 | ||
|
|
5866626b08 | ||
|
|
c495bca4ad | ||
|
|
413cc5b0c8 | ||
|
|
d2533d0efb | ||
|
|
3e0113fff4 | ||
|
|
9c42a41ed8 | ||
|
|
2fe07c203e | ||
|
|
6882943e39 | ||
|
|
b98aa3b4f1 | ||
|
|
6cd6b921ac | ||
|
|
908faf8cd7 | ||
|
|
b9731767af | ||
|
|
36a10875c8 | ||
|
|
f7ca03ae87 | ||
|
|
c15d76a40f | ||
|
|
5369a5c54d | ||
|
|
9d187f0238 | ||
|
|
c095c87e11 | ||
|
|
73d4a57d47 | ||
|
|
5f8888e116 | ||
|
|
9dbb8ef4aa | ||
|
|
52f4d6dd78 | ||
|
|
e4aa882ec5 | ||
|
|
38b1e8ee20 | ||
|
|
81d4cafb32 | ||
|
|
1afca33eac | ||
|
|
95461e8b22 | ||
|
|
0ed8b838a9 | ||
|
|
9becba5540 | ||
|
|
3511904aad | ||
|
|
b0d41e386e | ||
|
|
91c3362315 | ||
|
|
14852810b4 | ||
|
|
542d51895f | ||
|
|
68651a2329 | ||
|
|
5258785c81 | ||
|
|
1a5425779b | ||
|
|
a28390542c | ||
|
|
eeb53bc143 | ||
|
|
e21aa0fda3 | ||
|
|
9f1007e554 | ||
|
|
4b939c23e4 | ||
|
|
7138de7b55 | ||
|
|
b4cf57a581 | ||
|
|
da58afcea0 | ||
|
|
ce823c9f84 | ||
|
|
5e1581c2c3 | ||
|
|
50df2b78be | ||
|
|
c786f75389 | ||
|
|
7f9d94fe9a | ||
|
|
cf7aba36c8 | ||
|
|
3857cdc267 | ||
|
|
48648bc2f8 | ||
|
|
fe070ab5c3 | ||
|
|
8c33ac10bf | ||
|
|
3b79bac05b | ||
|
|
afc2039f22 | ||
|
|
13db4af345 | ||
|
|
64ee3e92ea | ||
|
|
afa3c72c40 | ||
|
|
1d7d7f57d0 | ||
|
|
fb5f25eeee | ||
|
|
c6069a627c | ||
|
|
1f2176dedc | ||
|
|
7bb2a489b2 | ||
|
|
e9971d356b | ||
|
|
5129ef22c2 | ||
|
|
3654aeaa4f | ||
|
|
f258a21a63 | ||
|
|
fd777bb210 | ||
|
|
3da1bf8ca1 | ||
|
|
bbea4b2b53 | ||
|
|
32516c768e | ||
|
|
1a32bdf92c | ||
|
|
2499b1b139 | ||
|
|
e7610eadfe | ||
|
|
732f5468d3 | ||
|
|
bbfe0b8d04 | ||
|
|
46e5583993 | ||
|
|
bf62acf033 | ||
|
|
586198ccea | ||
|
|
d95962cd5d | ||
|
|
79d5e5593f | ||
|
|
b5874273ce | ||
|
|
8092106abc | ||
|
|
eab2201f80 | ||
|
|
e85b68ef53 | ||
|
|
6e613cf3de | ||
|
|
afb8154eab | ||
|
|
1d06e41f04 | ||
|
|
43dd8e62fc | ||
|
|
80c6dfc19f | ||
|
|
d927c67f9d | ||
|
|
20fe928914 | ||
|
|
54aeb8e4c0 | ||
|
|
73067fd24f | ||
|
|
e37f7be97e | ||
|
|
b33a5294ea | ||
|
|
be12392fba | ||
|
|
8f35e3086c | ||
|
|
e323ed5a9a | ||
|
|
6bb61ee9ef | ||
|
|
0f7fbb85d6 | ||
|
|
62dc530773 | ||
|
|
e4c9fd29a3 | ||
|
|
de37e088f2 | ||
|
|
f0ac925fa7 | ||
|
|
0981d2e566 | ||
|
|
f14047dae5 | ||
|
|
b0056f5bd0 | ||
|
|
5dea0f2aa4 | ||
|
|
989fb4472a | ||
|
|
9ff9d04a69 | ||
|
|
edc3e0efeb | ||
|
|
f9569f3cd8 | ||
|
|
a3a2c6b0d9 | ||
|
|
35801f938e | ||
|
|
cc3ca63dbf | ||
|
|
049797d40a | ||
|
|
41ef34ae40 | ||
|
|
b169a309f9 | ||
|
|
7f40ae7876 | ||
|
|
327dcd3622 | ||
|
|
ffc12f63ec | ||
|
|
80be5e5463 | ||
|
|
7abf968d6f | ||
|
|
6eb38e02a8 | ||
|
|
51a86f61be | ||
|
|
b5cf603895 | ||
|
|
1e0c336d29 | ||
|
|
9e095251b7 | ||
|
|
da3b9f831e | ||
|
|
7b258c9681 | ||
|
|
8c78449a9e | ||
|
|
005665867d | ||
|
|
b64525694b | ||
|
|
448abb61eb | ||
|
|
03ec3fed2b | ||
|
|
a5330fe0c5 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
Before you do a feature request please check and make sure that it isn't possible
|
||||
through some other means. The JavaScript enabled console is a powerful feature
|
||||
in the right hands. Please check our [Bitchin' tricks](https://github.com/ethereum/go-ethereum/wiki/bitchin-tricks) wiki page for more info
|
||||
in the right hands. Please check our [Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info
|
||||
and help.
|
||||
|
||||
## Contributing
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -35,6 +35,7 @@ profile.cov
|
||||
.idea
|
||||
|
||||
# dashboard
|
||||
/dashboard/assets/flow-typed
|
||||
/dashboard/assets/node_modules
|
||||
/dashboard/assets/stats.json
|
||||
/dashboard/assets/public/bundle.js
|
||||
/dashboard/assets/bundle.js
|
||||
|
||||
14
.mailmap
14
.mailmap
@@ -65,7 +65,8 @@ Enrique Fynn <enriquefynn@gmail.com>
|
||||
|
||||
Vincent G <caktux@gmail.com>
|
||||
|
||||
RJ Catalano <rj@erisindustries.com>
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
|
||||
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
|
||||
@@ -109,3 +110,14 @@ Frank Wang <eternnoir@gmail.com>
|
||||
Gary Rong <garyrong0905@gmail.com>
|
||||
|
||||
Guillaume Nicolas <guin56@gmail.com>
|
||||
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com> <sorin@users.noreply.github.com>
|
||||
|
||||
Valentin Wüstholz <wuestholz@gmail.com>
|
||||
Valentin Wüstholz <wuestholz@gmail.com> <wuestholz@users.noreply.github.com>
|
||||
|
||||
Armin Braun <me@obrown.io>
|
||||
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com> <ernestodeltoro@users.noreply.github.com>
|
||||
|
||||
39
.travis.yml
39
.travis.yml
@@ -8,7 +8,6 @@ matrix:
|
||||
sudo: required
|
||||
go: 1.7.x
|
||||
script:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
@@ -20,7 +19,6 @@ matrix:
|
||||
sudo: required
|
||||
go: 1.8.x
|
||||
script:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
@@ -33,7 +31,6 @@ matrix:
|
||||
sudo: required
|
||||
go: 1.9.x
|
||||
script:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
@@ -42,8 +39,8 @@ matrix:
|
||||
|
||||
- os: osx
|
||||
go: 1.9.x
|
||||
sudo: required
|
||||
script:
|
||||
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
||||
- brew update
|
||||
- brew install caskroom/cask/brew-cask
|
||||
- brew cask install osxfuse
|
||||
@@ -53,15 +50,12 @@ matrix:
|
||||
# This builder only tests code linters on latest version of Go
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
go: 1.9.x
|
||||
env:
|
||||
- lint
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
- go run build/ci.go lint
|
||||
|
||||
# This builder does the Ubuntu PPA and Linux Azure uploads
|
||||
@@ -72,6 +66,8 @@ matrix:
|
||||
env:
|
||||
- ubuntu-ppa
|
||||
- azure-linux
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
@@ -92,24 +88,25 @@ matrix:
|
||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||
- sudo ln -s /usr/include/asm-generic /usr/include/asm
|
||||
|
||||
- GOARM=5 CC=arm-linux-gnueabi-gcc go run build/ci.go install -arch arm
|
||||
- GOARM=5 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
||||
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
- GOARM=6 CC=arm-linux-gnueabi-gcc go run build/ci.go install -arch arm
|
||||
- GOARM=6 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
||||
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
- GOARM=7 CC=arm-linux-gnueabihf-gcc go run build/ci.go install -arch arm
|
||||
- GOARM=7 go run build/ci.go install -arch arm -cc arm-linux-gnueabihf-gcc
|
||||
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
- CC=aarch64-linux-gnu-gcc go run build/ci.go install -arch arm64
|
||||
- go run build/ci.go install -arch arm64 -cc aarch64-linux-gnu-gcc
|
||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
|
||||
# This builder does the Linux Azure MIPS xgo uploads
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
go: 1.9.x
|
||||
env:
|
||||
- azure-linux-mips
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
|
||||
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
|
||||
@@ -146,6 +143,8 @@ matrix:
|
||||
env:
|
||||
- azure-android
|
||||
- maven-android
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
before_install:
|
||||
- curl https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz | tar -xz
|
||||
- export PATH=`pwd`/go/bin:$PATH
|
||||
@@ -169,6 +168,8 @@ matrix:
|
||||
- azure-osx
|
||||
- azure-ios
|
||||
- cocoapods-ios
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
||||
@@ -184,6 +185,8 @@ matrix:
|
||||
- xctool -version
|
||||
- xcrun simctl list
|
||||
|
||||
# Workaround for https://github.com/golang/go/issues/23749
|
||||
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
|
||||
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
||||
|
||||
# This builder does the Azure archive purges to avoid accumulating junk
|
||||
@@ -193,15 +196,11 @@ matrix:
|
||||
go: 1.9.x
|
||||
env:
|
||||
- azure-purge
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
|
||||
92
AUTHORS
92
AUTHORS
@@ -1,85 +1,173 @@
|
||||
# This is the official list of go-ethereum authors for copyright purposes.
|
||||
|
||||
Afri Schoedon <5chdn@users.noreply.github.com>
|
||||
Agustin Armellini Fischer <armellini13@gmail.com>
|
||||
Airead <fgh1987168@gmail.com>
|
||||
Alan Chen <alanchchen@users.noreply.github.com>
|
||||
Alejandro Isaza <alejandro.isaza@gmail.com>
|
||||
Ales Katona <ales@coinbase.com>
|
||||
Alex Leverington <alex@ethdev.com>
|
||||
Alex Wu <wuyiding@gmail.com>
|
||||
Alexandre Van de Sande <alex.vandesande@ethdev.com>
|
||||
Ali Hajimirza <Ali92hm@users.noreply.github.com>
|
||||
Anton Evangelatov <anton.evangelatov@gmail.com>
|
||||
Arba Sasmoyo <arba.sasmoyo@gmail.com>
|
||||
Armani Ferrante <armaniferrante@berkeley.edu>
|
||||
Armin Braun <me@obrown.io>
|
||||
Aron Fischer <github@aron.guru>
|
||||
Bas van Kervel <bas@ethdev.com>
|
||||
Benjamin Brent <benjamin@benjaminbrent.com>
|
||||
Benoit Verkindt <benoit.verkindt@gmail.com>
|
||||
Bo <bohende@gmail.com>
|
||||
Bo Ye <boy.e.computer.1982@outlook.com>
|
||||
Bob Glickstein <bobg@users.noreply.github.com>
|
||||
Brian Schroeder <bts@gmail.com>
|
||||
Casey Detrio <cdetrio@gmail.com>
|
||||
Chase Wright <mysticryuujin@gmail.com>
|
||||
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||
Daniel A. Nagy <nagy.da@gmail.com>
|
||||
Daniel Sloof <goapsychadelic@gmail.com>
|
||||
Darrel Herbst <dherbst@gmail.com>
|
||||
Dave Appleton <calistralabs@gmail.com>
|
||||
Diego Siqueira <DiSiqueira@users.noreply.github.com>
|
||||
Dmitry Shulyak <yashulyak@gmail.com>
|
||||
Egon Elbre <egonelbre@gmail.com>
|
||||
Elias Naur <elias.naur@gmail.com>
|
||||
Elliot Shepherd <elliot@identitii.com>
|
||||
Enrique Fynn <enriquefynn@gmail.com>
|
||||
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||
Ethan Buchman <ethan@coinculture.info>
|
||||
Eugene Valeyev <evgen.povt@gmail.com>
|
||||
Evangelos Pappas <epappas@evalonlabs.com>
|
||||
Evgeny Danilenko <6655321@bk.ru>
|
||||
Fabian Vogelsteller <fabian@frozeman.de>
|
||||
Fabio Barone <fabio.barone.co@gmail.com>
|
||||
Fabio Berger <fabioberger1991@gmail.com>
|
||||
FaceHo <facehoshi@gmail.com>
|
||||
Felix Lange <fjl@twurst.com>
|
||||
Fiisio <liangcszzu@163.com>
|
||||
Frank Wang <eternnoir@gmail.com>
|
||||
Furkan KAMACI <furkankamaci@gmail.com>
|
||||
Gary Rong <garyrong0905@gmail.com>
|
||||
George Ornbo <george@shapeshed.com>
|
||||
Gregg Dourgarian <greggd@tempworks.com>
|
||||
Guillaume Ballet <gballet@gmail.com>
|
||||
Guillaume Nicolas <guin56@gmail.com>
|
||||
Gustav Simonsson <gustav.simonsson@gmail.com>
|
||||
Hao Bryan Cheng <haobcheng@gmail.com>
|
||||
Henning Diedrich <hd@eonblast.com>
|
||||
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
|
||||
Ivan Daniluk <ivan.daniluk@gmail.com>
|
||||
Jae Kwon <jkwon.work@gmail.com>
|
||||
Jamie Pitts <james.pitts@gmail.com>
|
||||
Janoš Guljaš <janos@users.noreply.github.com>
|
||||
Jason Carver <jacarver@linkedin.com>
|
||||
Jay Guo <guojiannan1101@gmail.com>
|
||||
Jeff R. Allen <jra@nella.org>
|
||||
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||
Jens Agerberg <github@agerberg.me>
|
||||
Jia Chenhui <jiachenhui1989@gmail.com>
|
||||
Jim McDonald <Jim@mcdee.net>
|
||||
Joel Burget <joelburget@gmail.com>
|
||||
Jonathan Brown <jbrown@bluedroplet.com>
|
||||
Joseph Chow <ethereum@outlook.com>
|
||||
Justin Clark-Casey <justincc@justincc.org>
|
||||
Justin Drake <drakefjustin@gmail.com>
|
||||
Kenji Siu <kenji@isuntv.com>
|
||||
Kobi Gurkan <kobigurk@gmail.com>
|
||||
Konrad Feldmeier <konrad@brainbot.com>
|
||||
Kurkó Mihály <kurkomisi@users.noreply.github.com>
|
||||
Kyuntae Ethan Kim <ethan.kyuntae.kim@gmail.com>
|
||||
Lefteris Karapetsas <lefteris@refu.co>
|
||||
Leif Jurvetson <leijurv@gmail.com>
|
||||
Leo Shklovskii <leo@thermopylae.net>
|
||||
Lewis Marshall <lewis@lmars.net>
|
||||
Lio李欧 <lionello@users.noreply.github.com>
|
||||
Louis Holbrook <dev@holbrook.no>
|
||||
Luca Zeug <luclu@users.noreply.github.com>
|
||||
Magicking <s@6120.eu>
|
||||
Maran Hidskes <maran.hidskes@gmail.com>
|
||||
Marek Kotewicz <marek.kotewicz@gmail.com>
|
||||
Mark <markya0616@gmail.com>
|
||||
Martin Holst Swende <martin@swende.se>
|
||||
Matthew Di Ferrante <mattdf@users.noreply.github.com>
|
||||
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
|
||||
Maximilian Meister <mmeister@suse.de>
|
||||
Micah Zoltu <micah@zoltu.net>
|
||||
Michael Ruminer <michael.ruminer+github@gmail.com>
|
||||
Miguel Mota <miguelmota2@gmail.com>
|
||||
Miya Chen <miyatlchen@gmail.com>
|
||||
Nchinda Nchinda <nchinda2@gmail.com>
|
||||
Nick Dodson <silentcicero@outlook.com>
|
||||
Nick Johnson <arachnid@notdot.net>
|
||||
Nicolas Guillaume <gunicolas@sqli.com>
|
||||
Noman <noman@noman.land>
|
||||
Oli Bye <olibye@users.noreply.github.com>
|
||||
Paul Litvak <litvakpol@012.net.il>
|
||||
Paulo L F Casaretto <pcasaretto@gmail.com>
|
||||
Paweł Bylica <chfast@gmail.com>
|
||||
Peter Pratscher <pratscher@gmail.com>
|
||||
Petr Mikusek <petr@mikusek.info>
|
||||
Péter Szilágyi <peterke@gmail.com>
|
||||
RJ Catalano <rj@erisindustries.com>
|
||||
RJ Catalano <catalanor0220@gmail.com>
|
||||
Ramesh Nair <ram@hiddentao.com>
|
||||
Ricardo Catalinas Jiménez <r@untroubled.be>
|
||||
Ricardo Domingos <ricardohsd@gmail.com>
|
||||
Richard Hart <richardhart92@gmail.com>
|
||||
Rob <robert@rojotek.com>
|
||||
Robert Zaremba <robert.zaremba@scale-it.pl>
|
||||
Russ Cox <rsc@golang.org>
|
||||
Rémy Roy <remyroy@remyroy.com>
|
||||
S. Matthew English <s-matthew-english@users.noreply.github.com>
|
||||
Shintaro Kaneko <kaneshin0120@gmail.com>
|
||||
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||
Stein Dekker <dekker.stein@gmail.com>
|
||||
Steve Waldman <swaldman@mchange.com>
|
||||
Steven Roose <stevenroose@gmail.com>
|
||||
Taylor Gerring <taylor.gerring@gmail.com>
|
||||
Thomas Bocek <tom@tomp2p.net>
|
||||
Ti Zhou <tizhou1986@gmail.com>
|
||||
Tosh Camille <tochecamille@gmail.com>
|
||||
Valentin Wüstholz <wuestholz@users.noreply.github.com>
|
||||
Valentin Wüstholz <wuestholz@gmail.com>
|
||||
Victor Farazdagi <simple.square@gmail.com>
|
||||
Victor Tran <vu.tran54@gmail.com>
|
||||
Viktor Trón <viktor.tron@gmail.com>
|
||||
Ville Sundell <github@solarius.fi>
|
||||
Vincent G <caktux@gmail.com>
|
||||
Vitalik Buterin <v@buterin.com>
|
||||
Vitaly V <vvelikodny@gmail.com>
|
||||
Vivek Anand <vivekanand1101@users.noreply.github.com>
|
||||
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||
Yohann Léon <sybiload@gmail.com>
|
||||
Yoichi Hirai <i@yoichihirai.com>
|
||||
Yondon Fu <yondon.fu@gmail.com>
|
||||
Zach <zach.ramsay@gmail.com>
|
||||
Zahoor Mohamed <zahoor@zahoor.in>
|
||||
Zoe Nolan <github@zoenolan.org>
|
||||
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||
am2rican5 <am2rican5@gmail.com>
|
||||
ayeowch <ayeowch@gmail.com>
|
||||
b00ris <b00ris@mail.ru>
|
||||
bailantaotao <Edwin@maicoin.com>
|
||||
baizhenxuan <nkbai@163.com>
|
||||
bloonfield <bloonfield@163.com>
|
||||
changhong <changhong.yu@shanbay.com>
|
||||
evgk <evgeniy.kamyshev@gmail.com>
|
||||
ferhat elmas <elmas.ferhat@gmail.com>
|
||||
holisticode <holistic.computing@gmail.com>
|
||||
jtakalai <juuso.takalainen@streamr.com>
|
||||
ken10100147 <sunhongping@kanjian.com>
|
||||
ligi <ligi@ligi.de>
|
||||
mark.lin <mark@maicoin.com>
|
||||
necaremus <necaremus@gmail.com>
|
||||
njupt-moon <1015041018@njupt.edu.cn>
|
||||
nkbai <nkbai@163.com>
|
||||
rhaps107 <dod-source@yandex.ru>
|
||||
slumber1122 <slumber1122@gmail.com>
|
||||
sunxiaojun2014 <sunxiaojun-xy@360.cn>
|
||||
terasum <terasum@163.com>
|
||||
tsarpaul <Litvakpol@012.net.il>
|
||||
xiekeyang <xiekeyang@users.noreply.github.com>
|
||||
yoza <yoza.is12s@gmail.com>
|
||||
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||
|
||||
6
Makefile
6
Makefile
@@ -45,9 +45,13 @@ clean:
|
||||
|
||||
devtools:
|
||||
env GOBIN= go get -u golang.org/x/tools/cmd/stringer
|
||||
env GOBIN= go get -u github.com/jteeuwen/go-bindata/go-bindata
|
||||
env GOBIN= go get -u github.com/kevinburke/go-bindata/go-bindata
|
||||
env GOBIN= go get -u github.com/fjl/gencodec
|
||||
env GOBIN= go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
env GOBIN= go install ./cmd/abigen
|
||||
@type "npm" 2> /dev/null || echo 'Please install node.js and npm'
|
||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||
|
||||
# Cross Compilation Targets (xgo)
|
||||
|
||||
|
||||
29
README.md
29
README.md
@@ -56,16 +56,14 @@ the user doesn't care about years-old historical data, so we can fast-sync quick
|
||||
state of the network. To do so:
|
||||
|
||||
```
|
||||
$ geth --fast --cache=512 console
|
||||
$ geth console
|
||||
```
|
||||
|
||||
This command will:
|
||||
|
||||
* Start geth in fast sync mode (`--fast`), causing it to download more data in exchange for avoiding
|
||||
processing the entire history of the Ethereum network, which is very CPU intensive.
|
||||
* Bump the memory allowance of the database to 512MB (`--cache=512`), which can help significantly in
|
||||
sync times especially for HDD users. This flag is optional and you can set it as high or as low as
|
||||
you'd like, though we'd recommend the 512MB - 2GB range.
|
||||
* Start geth in fast sync mode (default, can be changed with the `--syncmode` flag), causing it to
|
||||
download more data in exchange for avoiding processing the entire history of the Ethereum network,
|
||||
which is very CPU intensive.
|
||||
* Start up Geth's built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console),
|
||||
(via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API)
|
||||
as well as Geth's own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs).
|
||||
@@ -80,12 +78,11 @@ entire system. In other words, instead of attaching to the main network, you wan
|
||||
network with your node, which is fully equivalent to the main network, but with play-Ether only.
|
||||
|
||||
```
|
||||
$ geth --testnet --fast --cache=512 console
|
||||
$ geth --testnet console
|
||||
```
|
||||
|
||||
The `--fast`, `--cache` flags and `console` subcommand have the exact same meaning as above and they
|
||||
are equally useful on the testnet too. Please see above for their explanations if you've skipped to
|
||||
here.
|
||||
The `console` subcommand have the exact same meaning as above and they are equally useful on the
|
||||
testnet too. Please see above for their explanations if you've skipped to here.
|
||||
|
||||
Specifying the `--testnet` flag however will reconfigure your Geth instance a bit:
|
||||
|
||||
@@ -102,6 +99,14 @@ over between the main network and test network, you should make sure to always u
|
||||
for play-money and real-money. Unless you manually move accounts, Geth will by default correctly
|
||||
separate the two networks and will not make any accounts available between them.*
|
||||
|
||||
### Full node on the Rinkeby test network
|
||||
|
||||
The above test network is a cross client one based on the ethash proof-of-work consensus algorithm. As such, it has certain extra overhead and is more susceptible to reorganization attacks due to the network's low difficulty / security. Go Ethereum also supports connecting to a proof-of-authority based test network called [*Rinkeby*](https://www.rinkeby.io) (operated by members of the community). This network is lighter, more secure, but is only supported by go-ethereum.
|
||||
|
||||
```
|
||||
$ geth --rinkeby console
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a configuration file via:
|
||||
@@ -125,10 +130,10 @@ One of the quickest ways to get Ethereum up and running on your machine is by us
|
||||
```
|
||||
docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
|
||||
-p 8545:8545 -p 30303:30303 \
|
||||
ethereum/client-go --fast --cache=512
|
||||
ethereum/client-go
|
||||
```
|
||||
|
||||
This will start geth in fast sync mode with a DB memory allowance of 512MB just as the above command does. It will also create a persistent volume in your home directory for saving your blockchain as well as map the default ports. There is also an `alpine` tag available for a slim version of the image.
|
||||
This will start geth in fast-sync mode with a DB memory allowance of 1GB just as the above command does. It will also create a persistent volume in your home directory for saving your blockchain as well as map the default ports. There is also an `alpine` tag available for a slim version of the image.
|
||||
|
||||
Do not forget `--rpcaddr 0.0.0.0`, if you want to access RPC from other containers and/or hosts. By default, `geth` binds to the local interface and RPC endpoints is not accessible from the outside.
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -50,57 +51,52 @@ func JSON(reader io.Reader) (ABI, error) {
|
||||
// methods string signature. (signature = baz(uint32,string32))
|
||||
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||
// Fetch the ABI of the requested method
|
||||
var method Method
|
||||
|
||||
if name == "" {
|
||||
method = abi.Constructor
|
||||
} else {
|
||||
m, exist := abi.Methods[name]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("method '%s' not found", name)
|
||||
// constructor
|
||||
arguments, err := abi.Constructor.Inputs.Pack(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
method = m
|
||||
return arguments, nil
|
||||
|
||||
}
|
||||
arguments, err := method.pack(args...)
|
||||
method, exist := abi.Methods[name]
|
||||
if !exist {
|
||||
return nil, fmt.Errorf("method '%s' not found", name)
|
||||
}
|
||||
|
||||
arguments, err := method.Inputs.Pack(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pack up the method ID too if not a constructor and return
|
||||
if name == "" {
|
||||
return arguments, nil
|
||||
}
|
||||
return append(method.Id(), arguments...), nil
|
||||
}
|
||||
|
||||
// Unpack output in v according to the abi specification
|
||||
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||
if err = bytesAreProper(output); err != nil {
|
||||
return err
|
||||
if len(output) == 0 {
|
||||
return fmt.Errorf("abi: unmarshalling empty output")
|
||||
}
|
||||
// since there can't be naming collisions with contracts and events,
|
||||
// we need to decide whether we're calling a method or an event
|
||||
var unpack unpacker
|
||||
if method, ok := abi.Methods[name]; ok {
|
||||
unpack = method
|
||||
if len(output)%32 != 0 {
|
||||
return fmt.Errorf("abi: improperly formatted output")
|
||||
}
|
||||
return method.Outputs.Unpack(v, output)
|
||||
} else if event, ok := abi.Events[name]; ok {
|
||||
unpack = event
|
||||
} else {
|
||||
return fmt.Errorf("abi: could not locate named method or event.")
|
||||
return event.Inputs.Unpack(v, output)
|
||||
}
|
||||
|
||||
// requires a struct to unpack into for a tuple return...
|
||||
if unpack.isTupleReturn() {
|
||||
return unpack.tupleUnpack(v, output)
|
||||
}
|
||||
return unpack.singleUnpack(v, output)
|
||||
return fmt.Errorf("abi: could not locate named method or event")
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface
|
||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||
var fields []struct {
|
||||
Type string
|
||||
Name string
|
||||
Constant bool
|
||||
Indexed bool
|
||||
Anonymous bool
|
||||
Inputs []Argument
|
||||
Outputs []Argument
|
||||
@@ -137,3 +133,14 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MethodById looks up a method by the 4-byte id
|
||||
// returns nil if none found
|
||||
func (abi *ABI) MethodById(sigdata []byte) *Method {
|
||||
for _, method := range abi.Methods {
|
||||
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||
return &method
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@ package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
@@ -74,9 +76,24 @@ func TestReader(t *testing.T) {
|
||||
}
|
||||
|
||||
// deep equal fails for some reason
|
||||
t.Skip()
|
||||
if !reflect.DeepEqual(abi, exp) {
|
||||
t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp)
|
||||
for name, expM := range exp.Methods {
|
||||
gotM, exist := abi.Methods[name]
|
||||
if !exist {
|
||||
t.Errorf("Missing expected method %v", name)
|
||||
}
|
||||
if !reflect.DeepEqual(gotM, expM) {
|
||||
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
|
||||
}
|
||||
}
|
||||
|
||||
for name, gotM := range abi.Methods {
|
||||
expM, exist := exp.Methods[name]
|
||||
if !exist {
|
||||
t.Errorf("Found extra method %v", name)
|
||||
}
|
||||
if !reflect.DeepEqual(gotM, expM) {
|
||||
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +365,188 @@ func TestInputVariableInputLength(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
||||
{ "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
||||
{ "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] },
|
||||
{ "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] },
|
||||
{ "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
|
||||
]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2]
|
||||
strin := "hello world"
|
||||
arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
offset := make([]byte, 32)
|
||||
offset[31] = 96
|
||||
length := make([]byte, 32)
|
||||
length[31] = byte(len(strin))
|
||||
strvalue := common.RightPadBytes([]byte(strin), 32)
|
||||
arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32)
|
||||
arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32)
|
||||
exp := append(offset, arrinvalue1...)
|
||||
exp = append(exp, arrinvalue2...)
|
||||
exp = append(exp, append(length, strvalue...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
fixedArrStrPack = fixedArrStrPack[4:]
|
||||
if !bytes.Equal(fixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, fixedArrStrPack)
|
||||
}
|
||||
|
||||
// test byte array, fixed array uint256[2]
|
||||
bytesin := []byte(strin)
|
||||
arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
offset = make([]byte, 32)
|
||||
offset[31] = 96
|
||||
length = make([]byte, 32)
|
||||
length[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32)
|
||||
arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32)
|
||||
exp = append(offset, arrinvalue1...)
|
||||
exp = append(exp, arrinvalue2...)
|
||||
exp = append(exp, append(length, strvalue...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
fixedArrBytesPack = fixedArrBytesPack[4:]
|
||||
if !bytes.Equal(fixedArrBytesPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, fixedArrBytesPack)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2], dynamic array uint256[]
|
||||
strin = "hello world"
|
||||
fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||
mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
stroffset := make([]byte, 32)
|
||||
stroffset[31] = 128
|
||||
strlength := make([]byte, 32)
|
||||
strlength[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
fixedarrinvalue1 := common.LeftPadBytes(fixedarrin[0].Bytes(), 32)
|
||||
fixedarrinvalue2 := common.LeftPadBytes(fixedarrin[1].Bytes(), 32)
|
||||
dynarroffset := make([]byte, 32)
|
||||
dynarroffset[31] = byte(160 + ((len(strin)/32)+1)*32)
|
||||
dynarrlength := make([]byte, 32)
|
||||
dynarrlength[31] = byte(len(dynarrin))
|
||||
dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32)
|
||||
dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32)
|
||||
dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32)
|
||||
exp = append(stroffset, fixedarrinvalue1...)
|
||||
exp = append(exp, fixedarrinvalue2...)
|
||||
exp = append(exp, dynarroffset...)
|
||||
exp = append(exp, append(strlength, strvalue...)...)
|
||||
dynarrarg := append(dynarrlength, dynarrinvalue1...)
|
||||
dynarrarg = append(dynarrarg, dynarrinvalue2...)
|
||||
dynarrarg = append(dynarrarg, dynarrinvalue3...)
|
||||
exp = append(exp, dynarrarg...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
mixedArrStrPack = mixedArrStrPack[4:]
|
||||
if !bytes.Equal(mixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, mixedArrStrPack)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2], fixed array uint256[3]
|
||||
strin = "hello world"
|
||||
fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||
doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
stroffset = make([]byte, 32)
|
||||
stroffset[31] = 192
|
||||
strlength = make([]byte, 32)
|
||||
strlength[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
fixedarrin1value1 := common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
|
||||
fixedarrin1value2 := common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
|
||||
fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
|
||||
fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
|
||||
fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
|
||||
exp = append(stroffset, fixedarrin1value1...)
|
||||
exp = append(exp, fixedarrin1value2...)
|
||||
exp = append(exp, fixedarrin2value1...)
|
||||
exp = append(exp, fixedarrin2value2...)
|
||||
exp = append(exp, fixedarrin2value3...)
|
||||
exp = append(exp, append(strlength, strvalue...)...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
doubleFixedArrStrPack = doubleFixedArrStrPack[4:]
|
||||
if !bytes.Equal(doubleFixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, doubleFixedArrStrPack)
|
||||
}
|
||||
|
||||
// test string, fixed array uint256[2], dynamic array uint256[], fixed array uint256[3]
|
||||
strin = "hello world"
|
||||
fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||
fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||
multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// generate expected output
|
||||
stroffset = make([]byte, 32)
|
||||
stroffset[31] = 224
|
||||
strlength = make([]byte, 32)
|
||||
strlength[31] = byte(len(strin))
|
||||
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||
fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
|
||||
fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
|
||||
dynarroffset = U256(big.NewInt(int64(256 + ((len(strin)/32)+1)*32)))
|
||||
dynarrlength = make([]byte, 32)
|
||||
dynarrlength[31] = byte(len(dynarrin))
|
||||
dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32)
|
||||
dynarrinvalue2 = common.LeftPadBytes(dynarrin[1].Bytes(), 32)
|
||||
fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
|
||||
fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
|
||||
fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
|
||||
exp = append(stroffset, fixedarrin1value1...)
|
||||
exp = append(exp, fixedarrin1value2...)
|
||||
exp = append(exp, dynarroffset...)
|
||||
exp = append(exp, fixedarrin2value1...)
|
||||
exp = append(exp, fixedarrin2value2...)
|
||||
exp = append(exp, fixedarrin2value3...)
|
||||
exp = append(exp, append(strlength, strvalue...)...)
|
||||
dynarrarg = append(dynarrlength, dynarrinvalue1...)
|
||||
dynarrarg = append(dynarrarg, dynarrinvalue2...)
|
||||
exp = append(exp, dynarrarg...)
|
||||
|
||||
// ignore first 4 bytes of the output. This is the function identifier
|
||||
multipleMixedArrStrPack = multipleMixedArrStrPack[4:]
|
||||
if !bytes.Equal(multipleMixedArrStrPack, exp) {
|
||||
t.Errorf("expected %x, got %x\n", exp, multipleMixedArrStrPack)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultFunctionParsing(t *testing.T) {
|
||||
const definition = `[{ "name" : "balance" }]`
|
||||
|
||||
@@ -418,3 +617,82 @@ func TestBareEvents(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnpackEvent is based on this contract:
|
||||
// contract T {
|
||||
// event received(address sender, uint amount, bytes memo);
|
||||
// function receive(bytes memo) external payable {
|
||||
// received(msg.sender, msg.value, memo);
|
||||
// }
|
||||
// }
|
||||
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
||||
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||
func TestUnpackEvent(t *testing.T) {
|
||||
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||
abi, err := JSON(strings.NewReader(abiJSON))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||
data, err := hex.DecodeString(hexdata)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data)%32 == 0 {
|
||||
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||
}
|
||||
|
||||
type ReceivedEvent struct {
|
||||
Address common.Address
|
||||
Amount *big.Int
|
||||
Memo []byte
|
||||
}
|
||||
var ev ReceivedEvent
|
||||
|
||||
err = abi.Unpack(&ev, "received", data)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Logf("len(data): %d; received event: %+v", len(data), ev)
|
||||
}
|
||||
}
|
||||
|
||||
func TestABI_MethodById(t *testing.T) {
|
||||
const abiJSON = `[
|
||||
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
|
||||
{"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]},
|
||||
{"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]},
|
||||
{"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]},
|
||||
{"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]},
|
||||
{"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]},
|
||||
{"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]},
|
||||
{"type":"function","name":"balance","constant":true},
|
||||
{"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]},
|
||||
{"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]},
|
||||
{"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]},
|
||||
{"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]},
|
||||
{"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]},
|
||||
{"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]},
|
||||
{"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]},
|
||||
{"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]},
|
||||
{"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]},
|
||||
{"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]},
|
||||
{"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]},
|
||||
{"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]},
|
||||
{"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]}
|
||||
]
|
||||
`
|
||||
abi, err := JSON(strings.NewReader(abiJSON))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, m := range abi.Methods {
|
||||
a := fmt.Sprintf("%v", m)
|
||||
b := fmt.Sprintf("%v", abi.MethodById(m.Id()))
|
||||
if a != b {
|
||||
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ package abi
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Argument holds the name of the argument and the corresponding type.
|
||||
@@ -29,7 +31,10 @@ type Argument struct {
|
||||
Indexed bool // indexed is only used by events
|
||||
}
|
||||
|
||||
func (a *Argument) UnmarshalJSON(data []byte) error {
|
||||
type Arguments []Argument
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler interface
|
||||
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
||||
var extarg struct {
|
||||
Name string
|
||||
Type string
|
||||
@@ -40,12 +45,206 @@ func (a *Argument) UnmarshalJSON(data []byte) error {
|
||||
return fmt.Errorf("argument json err: %v", err)
|
||||
}
|
||||
|
||||
a.Type, err = NewType(extarg.Type)
|
||||
argument.Type, err = NewType(extarg.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Name = extarg.Name
|
||||
a.Indexed = extarg.Indexed
|
||||
argument.Name = extarg.Name
|
||||
argument.Indexed = extarg.Indexed
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
|
||||
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
|
||||
func (arguments Arguments) LengthNonIndexed() int {
|
||||
out := 0
|
||||
for _, arg := range arguments {
|
||||
if !arg.Indexed {
|
||||
out++
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
||||
func (arguments Arguments) isTuple() bool {
|
||||
return len(arguments) > 1
|
||||
}
|
||||
|
||||
// Unpack performs the operation hexdata -> Go format
|
||||
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
||||
if arguments.isTuple() {
|
||||
return arguments.unpackTuple(v, data)
|
||||
}
|
||||
return arguments.unpackAtomic(v, data)
|
||||
}
|
||||
|
||||
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
||||
// make sure the passed value is arguments pointer
|
||||
valueOf := reflect.ValueOf(v)
|
||||
if reflect.Ptr != valueOf.Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
|
||||
var (
|
||||
value = valueOf.Elem()
|
||||
typ = value.Type()
|
||||
kind = value.Kind()
|
||||
)
|
||||
|
||||
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
||||
return err
|
||||
}
|
||||
// If the output interface is a struct, make sure names don't collide
|
||||
if kind == reflect.Struct {
|
||||
exists := make(map[string]bool)
|
||||
for _, arg := range arguments {
|
||||
field := capitalise(arg.Name)
|
||||
if field == "" {
|
||||
return fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
||||
}
|
||||
if exists[field] {
|
||||
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
|
||||
}
|
||||
exists[field] = true
|
||||
}
|
||||
}
|
||||
// `i` counts the nonindexed arguments.
|
||||
// `j` counts the number of complex types.
|
||||
// both `i` and `j` are used to to correctly compute `data` offset.
|
||||
|
||||
i, j := -1, 0
|
||||
for _, arg := range arguments {
|
||||
|
||||
if arg.Indexed {
|
||||
// can't read, continue
|
||||
continue
|
||||
}
|
||||
i++
|
||||
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if arg.Type.T == ArrayTy {
|
||||
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
|
||||
// we need to decrement 'j' because 'i' was incremented
|
||||
j += arg.Type.Size - 1
|
||||
}
|
||||
|
||||
reflectValue := reflect.ValueOf(marshalledValue)
|
||||
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
name := capitalise(arg.Name)
|
||||
for j := 0; j < typ.NumField(); j++ {
|
||||
// TODO read tags: `abi:"fieldName"`
|
||||
if typ.Field(j).Name == name {
|
||||
if err := set(value.Field(j), reflectValue, arg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if value.Len() < i {
|
||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||
}
|
||||
v := value.Index(i)
|
||||
if err := requireAssignable(v, reflectValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := set(v.Elem(), reflectValue, arg); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
||||
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
|
||||
// make sure the passed value is arguments pointer
|
||||
valueOf := reflect.ValueOf(v)
|
||||
if reflect.Ptr != valueOf.Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
arg := arguments[0]
|
||||
if arg.Indexed {
|
||||
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
||||
}
|
||||
|
||||
value := valueOf.Elem()
|
||||
|
||||
marshalledValue, err := toGoType(0, arg.Type, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return set(value, reflect.ValueOf(marshalledValue), arg)
|
||||
}
|
||||
|
||||
// Unpack performs the operation Go format -> Hexdata
|
||||
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||
// Make sure arguments match up and pack them
|
||||
abiArgs := arguments
|
||||
if len(args) != len(abiArgs) {
|
||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
||||
}
|
||||
|
||||
// variable input is the output appended at the end of packed
|
||||
// output. This is used for strings and bytes types input.
|
||||
var variableInput []byte
|
||||
|
||||
// input offset is the bytes offset for packed output
|
||||
inputOffset := 0
|
||||
for _, abiArg := range abiArgs {
|
||||
if abiArg.Type.T == ArrayTy {
|
||||
inputOffset += 32 * abiArg.Type.Size
|
||||
} else {
|
||||
inputOffset += 32
|
||||
}
|
||||
}
|
||||
|
||||
var ret []byte
|
||||
for i, a := range args {
|
||||
input := abiArgs[i]
|
||||
// pack the input
|
||||
packed, err := input.Type.pack(reflect.ValueOf(a))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check for a slice type (string, bytes, slice)
|
||||
if input.Type.requiresLengthPrefix() {
|
||||
// calculate the offset
|
||||
offset := inputOffset + len(variableInput)
|
||||
// set the offset
|
||||
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||
// Append the packed output to the variable input. The variable input
|
||||
// will be appended at the end of the input.
|
||||
variableInput = append(variableInput, packed...)
|
||||
} else {
|
||||
// append the packed value to the input
|
||||
ret = append(ret, packed...)
|
||||
}
|
||||
}
|
||||
// append the variable input at the end of the packed input
|
||||
ret = append(ret, variableInput...)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// capitalise makes the first character of a string upper case, also removing any
|
||||
// prefixing underscores from the variable names.
|
||||
func capitalise(input string) string {
|
||||
for len(input) > 0 && input[0] == '_' {
|
||||
input = input[1:]
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(input[:1]) + input[1:]
|
||||
}
|
||||
|
||||
@@ -52,12 +52,6 @@ type ContractCaller interface {
|
||||
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||
type DeployBackend interface {
|
||||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
// PendingContractCaller defines methods to perform contract calls on the pending state.
|
||||
// Call will try to discover this interface when access to the pending state is requested.
|
||||
// If the backend does not support the pending state, Call returns ErrNoPendingState.
|
||||
@@ -85,13 +79,34 @@ type ContractTransactor interface {
|
||||
// There is no guarantee that this is the true gas limit requirement as other
|
||||
// transactions may be added or removed by miners, but it should provide a basis
|
||||
// for setting a reasonable default.
|
||||
EstimateGas(ctx context.Context, call ethereum.CallMsg) (usedGas *big.Int, err error)
|
||||
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
||||
}
|
||||
|
||||
// ContractFilterer defines the methods needed to access log events using one-off
|
||||
// queries or continuous event subscriptions.
|
||||
type ContractFilterer interface {
|
||||
// FilterLogs executes a log filter operation, blocking during execution and
|
||||
// returning all the results in one batch.
|
||||
//
|
||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
|
||||
|
||||
// SubscribeFilterLogs creates a background log filtering operation, returning
|
||||
// a subscription immediately, which can be used to stream the found events.
|
||||
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
||||
}
|
||||
|
||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||
type DeployBackend interface {
|
||||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
// ContractBackend defines the methods needed to work with contracts on a read-write basis.
|
||||
type ContractBackend interface {
|
||||
ContractCaller
|
||||
ContractTransactor
|
||||
ContractFilterer
|
||||
}
|
||||
|
||||
@@ -30,11 +30,15 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
|
||||
@@ -53,6 +57,8 @@ type SimulatedBackend struct {
|
||||
pendingBlock *types.Block // Currently pending block that will be imported on request
|
||||
pendingState *state.StateDB // Currently pending state that will be the active on on request
|
||||
|
||||
events *filters.EventSystem // Event system for filtering log events live
|
||||
|
||||
config *params.ChainConfig
|
||||
}
|
||||
|
||||
@@ -62,8 +68,14 @@ func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
||||
database, _ := ethdb.NewMemDatabase()
|
||||
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
|
||||
genesis.MustCommit(database)
|
||||
blockchain, _ := core.NewBlockChain(database, genesis.Config, ethash.NewFaker(), vm.Config{})
|
||||
backend := &SimulatedBackend{database: database, blockchain: blockchain, config: genesis.Config}
|
||||
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
|
||||
|
||||
backend := &SimulatedBackend{
|
||||
database: database,
|
||||
blockchain: blockchain,
|
||||
config: genesis.Config,
|
||||
events: filters.NewEventSystem(new(event.TypeMux), &filterBackend{database, blockchain}, false),
|
||||
}
|
||||
backend.rollback()
|
||||
return backend
|
||||
}
|
||||
@@ -89,9 +101,11 @@ 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(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||
}
|
||||
|
||||
// CodeAt returns the code associated with a certain account in the blockchain.
|
||||
@@ -200,7 +214,7 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error
|
||||
|
||||
// EstimateGas executes the requested code against the currently pending block/state and
|
||||
// returns the used amount of gas.
|
||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (*big.Int, error) {
|
||||
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
@@ -210,16 +224,16 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
||||
hi uint64
|
||||
cap uint64
|
||||
)
|
||||
if call.Gas != nil && call.Gas.Uint64() >= params.TxGas {
|
||||
hi = call.Gas.Uint64()
|
||||
if call.Gas >= params.TxGas {
|
||||
hi = call.Gas
|
||||
} else {
|
||||
hi = b.pendingBlock.GasLimit().Uint64()
|
||||
hi = b.pendingBlock.GasLimit()
|
||||
}
|
||||
cap = hi
|
||||
|
||||
// Create a helper to check if a gas allowance results in an executable transaction
|
||||
executable := func(gas uint64) bool {
|
||||
call.Gas = new(big.Int).SetUint64(gas)
|
||||
call.Gas = gas
|
||||
|
||||
snapshot := b.pendingState.Snapshot()
|
||||
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
|
||||
@@ -242,21 +256,21 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
|
||||
// Reject the transaction as invalid if it still fails at the highest allowance
|
||||
if hi == cap {
|
||||
if !executable(hi) {
|
||||
return nil, errGasEstimationFailed
|
||||
return 0, errGasEstimationFailed
|
||||
}
|
||||
}
|
||||
return new(big.Int).SetUint64(hi), nil
|
||||
return hi, nil
|
||||
}
|
||||
|
||||
// callContract implemens common code between normal and pending contract calls.
|
||||
// callContract implements common code between normal and pending contract calls.
|
||||
// state is modified during execution, make sure to copy it if necessary.
|
||||
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, *big.Int, bool, error) {
|
||||
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) {
|
||||
// Ensure message is initialized properly.
|
||||
if call.GasPrice == nil {
|
||||
call.GasPrice = big.NewInt(1)
|
||||
}
|
||||
if call.Gas == nil || call.Gas.Sign() == 0 {
|
||||
call.Gas = big.NewInt(50000000)
|
||||
if call.Gas == 0 {
|
||||
call.Gas = 50000000
|
||||
}
|
||||
if call.Value == nil {
|
||||
call.Value = new(big.Int)
|
||||
@@ -271,9 +285,9 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
|
||||
// Create a new environment which holds all relevant information
|
||||
// about the transaction and calling mechanisms.
|
||||
vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{})
|
||||
gaspool := new(core.GasPool).AddGas(math.MaxBig256)
|
||||
ret, gasUsed, _, failed, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
|
||||
return ret, gasUsed, failed, err
|
||||
gaspool := new(core.GasPool).AddGas(math.MaxUint64)
|
||||
|
||||
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
|
||||
}
|
||||
|
||||
// SendTransaction updates the pending block to include the given transaction.
|
||||
@@ -291,29 +305,95 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
|
||||
}
|
||||
|
||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
for _, tx := range b.pendingBlock.Transactions() {
|
||||
block.AddTx(tx)
|
||||
}
|
||||
block.AddTx(tx)
|
||||
})
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||
return nil
|
||||
}
|
||||
|
||||
// JumpTimeInSeconds adds skip seconds to the clock
|
||||
// FilterLogs executes a log filter operation, blocking during execution and
|
||||
// returning all the results in one batch.
|
||||
//
|
||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
||||
// Initialize unset filter boundaried to run from genesis to chain head
|
||||
from := int64(0)
|
||||
if query.FromBlock != nil {
|
||||
from = query.FromBlock.Int64()
|
||||
}
|
||||
to := int64(-1)
|
||||
if query.ToBlock != nil {
|
||||
to = query.ToBlock.Int64()
|
||||
}
|
||||
// Construct and execute the filter
|
||||
filter := filters.New(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
|
||||
|
||||
logs, err := filter.Logs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make([]types.Log, len(logs))
|
||||
for i, log := range logs {
|
||||
res[i] = *log
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SubscribeFilterLogs creates a background log filtering operation, returning a
|
||||
// subscription immediately, which can be used to stream the found events.
|
||||
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
|
||||
// Subscribe to contract events
|
||||
sink := make(chan []*types.Log)
|
||||
|
||||
sub, err := b.events.SubscribeLogs(query, sink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Since we're getting logs in batches, we need to flatten them into a plain stream
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case logs := <-sink:
|
||||
for _, log := range logs {
|
||||
select {
|
||||
case ch <- *log:
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
// AdjustTime adds a time shift to the simulated clock.
|
||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
for _, tx := range b.pendingBlock.Transactions() {
|
||||
block.AddTx(tx)
|
||||
}
|
||||
block.OffsetTime(int64(adjustment.Seconds()))
|
||||
})
|
||||
statedb, _ := b.blockchain.State()
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -328,6 +408,47 @@ func (m callmsg) Nonce() uint64 { return 0 }
|
||||
func (m callmsg) CheckNonce() bool { return false }
|
||||
func (m callmsg) To() *common.Address { return m.CallMsg.To }
|
||||
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
|
||||
func (m callmsg) Gas() *big.Int { return m.CallMsg.Gas }
|
||||
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
|
||||
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
|
||||
func (m callmsg) Data() []byte { return m.CallMsg.Data }
|
||||
|
||||
// filterBackend implements filters.Backend to support filtering for logs without
|
||||
// taking bloom-bits acceleration structures into account.
|
||||
type filterBackend struct {
|
||||
db ethdb.Database
|
||||
bc *core.BlockChain
|
||||
}
|
||||
|
||||
func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db }
|
||||
func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") }
|
||||
|
||||
func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumber) (*types.Header, error) {
|
||||
if block == rpc.LatestBlockNumber {
|
||||
return fb.bc.CurrentHeader(), nil
|
||||
}
|
||||
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||
}
|
||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
|
||||
}
|
||||
|
||||
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
<-quit
|
||||
return nil
|
||||
})
|
||||
}
|
||||
func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
||||
return fb.bc.SubscribeChainEvent(ch)
|
||||
}
|
||||
func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
||||
return fb.bc.SubscribeRemovedLogsEvent(ch)
|
||||
}
|
||||
func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
||||
return fb.bc.SubscribeLogsEvent(ch)
|
||||
}
|
||||
|
||||
func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 }
|
||||
func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
// SignerFn is a signer function callback when a contract requires a method to
|
||||
@@ -50,11 +51,27 @@ type TransactOpts struct {
|
||||
|
||||
Value *big.Int // Funds to transfer along along the transaction (nil = 0 = no funds)
|
||||
GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
|
||||
GasLimit *big.Int // Gas limit to set for the transaction execution (nil = estimate + 10%)
|
||||
GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate)
|
||||
|
||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||
}
|
||||
|
||||
// FilterOpts is the collection of options to fine tune filtering for events
|
||||
// within a bound contract.
|
||||
type FilterOpts struct {
|
||||
Start uint64 // Start of the queried range
|
||||
End *uint64 // End of the range (nil = latest)
|
||||
|
||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||
}
|
||||
|
||||
// WatchOpts is the collection of options to fine tune subscribing for events
|
||||
// within a bound contract.
|
||||
type WatchOpts struct {
|
||||
Start *uint64 // Start of the queried range (nil = latest)
|
||||
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
|
||||
}
|
||||
|
||||
// BoundContract is the base wrapper object that reflects a contract on the
|
||||
// Ethereum network. It contains a collection of methods that are used by the
|
||||
// higher level contract bindings to operate.
|
||||
@@ -63,16 +80,18 @@ type BoundContract struct {
|
||||
abi abi.ABI // Reflect based ABI to access the correct Ethereum methods
|
||||
caller ContractCaller // Read interface to interact with the blockchain
|
||||
transactor ContractTransactor // Write interface to interact with the blockchain
|
||||
filterer ContractFilterer // Event filtering to interact with the blockchain
|
||||
}
|
||||
|
||||
// NewBoundContract creates a low level contract interface through which calls
|
||||
// and transactions may be made through.
|
||||
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor) *BoundContract {
|
||||
func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller, transactor ContractTransactor, filterer ContractFilterer) *BoundContract {
|
||||
return &BoundContract{
|
||||
address: address,
|
||||
abi: abi,
|
||||
caller: caller,
|
||||
transactor: transactor,
|
||||
filterer: filterer,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +99,7 @@ func NewBoundContract(address common.Address, abi abi.ABI, caller ContractCaller
|
||||
// deployment address with a Go wrapper.
|
||||
func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend ContractBackend, params ...interface{}) (common.Address, *types.Transaction, *BoundContract, error) {
|
||||
// Otherwise try to deploy the contract
|
||||
c := NewBoundContract(common.Address{}, abi, backend, backend)
|
||||
c := NewBoundContract(common.Address{}, abi, backend, backend, backend)
|
||||
|
||||
input, err := c.abi.Pack("", params...)
|
||||
if err != nil {
|
||||
@@ -189,7 +208,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
||||
}
|
||||
}
|
||||
gasLimit := opts.GasLimit
|
||||
if gasLimit == nil {
|
||||
if gasLimit == 0 {
|
||||
// Gas estimation cannot succeed without code for method invocations
|
||||
if contract != nil {
|
||||
if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil {
|
||||
@@ -225,6 +244,104 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
// FilterLogs filters contract logs for past blocks, returning the necessary
|
||||
// channels to construct a strongly typed bound iterator on top of them.
|
||||
func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
opts = new(FilterOpts)
|
||||
}
|
||||
// Append the event selector to the query parameters and construct the topic set
|
||||
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
|
||||
|
||||
topics, err := makeTopics(query...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Start the background filtering
|
||||
logs := make(chan types.Log, 128)
|
||||
|
||||
config := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{c.address},
|
||||
Topics: topics,
|
||||
FromBlock: new(big.Int).SetUint64(opts.Start),
|
||||
}
|
||||
if opts.End != nil {
|
||||
config.ToBlock = new(big.Int).SetUint64(*opts.End)
|
||||
}
|
||||
/* TODO(karalabe): Replace the rest of the method below with this when supported
|
||||
sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
|
||||
*/
|
||||
buff, err := c.filterer.FilterLogs(ensureContext(opts.Context), config)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
sub, err := event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
for _, log := range buff {
|
||||
select {
|
||||
case logs <- log:
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}), nil
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return logs, sub, nil
|
||||
}
|
||||
|
||||
// WatchLogs filters subscribes to contract logs for future blocks, returning a
|
||||
// subscription object that can be used to tear down the watcher.
|
||||
func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]interface{}) (chan types.Log, event.Subscription, error) {
|
||||
// Don't crash on a lazy user
|
||||
if opts == nil {
|
||||
opts = new(WatchOpts)
|
||||
}
|
||||
// Append the event selector to the query parameters and construct the topic set
|
||||
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
|
||||
|
||||
topics, err := makeTopics(query...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// Start the background filtering
|
||||
logs := make(chan types.Log, 128)
|
||||
|
||||
config := ethereum.FilterQuery{
|
||||
Addresses: []common.Address{c.address},
|
||||
Topics: topics,
|
||||
}
|
||||
if opts.Start != nil {
|
||||
config.FromBlock = new(big.Int).SetUint64(*opts.Start)
|
||||
}
|
||||
sub, err := c.filterer.SubscribeFilterLogs(ensureContext(opts.Context), config, logs)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return logs, sub, nil
|
||||
}
|
||||
|
||||
// UnpackLog unpacks a retrieved log into the provided output structure.
|
||||
func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) error {
|
||||
if len(log.Data) > 0 {
|
||||
if err := c.abi.Unpack(out, event, log.Data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var indexed abi.Arguments
|
||||
for _, arg := range c.abi.Events[event].Inputs {
|
||||
if arg.Indexed {
|
||||
indexed = append(indexed, arg)
|
||||
}
|
||||
}
|
||||
return parseTopics(out, indexed, log.Topics[1:])
|
||||
}
|
||||
|
||||
// ensureContext is a helper method to ensure a context is not nil, even if the
|
||||
// user specified it as such.
|
||||
func ensureContext(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
return context.TODO()
|
||||
|
||||
@@ -63,10 +63,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||
return r
|
||||
}, abis[i])
|
||||
|
||||
// Extract the call and transact methods, and sort them alphabetically
|
||||
// Extract the call and transact methods; events; and sort them alphabetically
|
||||
var (
|
||||
calls = make(map[string]*tmplMethod)
|
||||
transacts = make(map[string]*tmplMethod)
|
||||
events = make(map[string]*tmplEvent)
|
||||
)
|
||||
for _, original := range evmABI.Methods {
|
||||
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||
@@ -89,11 +90,33 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||
}
|
||||
// Append the methods to the call or transact lists
|
||||
if original.Const {
|
||||
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
|
||||
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||
} else {
|
||||
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)}
|
||||
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||
}
|
||||
}
|
||||
for _, original := range evmABI.Events {
|
||||
// Skip anonymous events as they don't support explicit filtering
|
||||
if original.Anonymous {
|
||||
continue
|
||||
}
|
||||
// Normalize the event for capital cases and non-anonymous outputs
|
||||
normalized := original
|
||||
normalized.Name = methodNormalizer[lang](original.Name)
|
||||
|
||||
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||
copy(normalized.Inputs, original.Inputs)
|
||||
for j, input := range normalized.Inputs {
|
||||
// Indexed fields are input, non-indexed ones are outputs
|
||||
if input.Indexed {
|
||||
if input.Name == "" {
|
||||
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Append the event to the accumulator list
|
||||
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
||||
}
|
||||
contracts[types[i]] = &tmplContract{
|
||||
Type: capitalise(types[i]),
|
||||
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
||||
@@ -101,6 +124,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||
Constructor: evmABI.Constructor,
|
||||
Calls: calls,
|
||||
Transacts: transacts,
|
||||
Events: events,
|
||||
}
|
||||
}
|
||||
// Generate the contract template data content and render it
|
||||
@@ -111,10 +135,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
funcs := map[string]interface{}{
|
||||
"bindtype": bindType[lang],
|
||||
"namedtype": namedType[lang],
|
||||
"capitalise": capitalise,
|
||||
"decapitalise": decapitalise,
|
||||
"bindtype": bindType[lang],
|
||||
"bindtopictype": bindTopicType[lang],
|
||||
"namedtype": namedType[lang],
|
||||
"capitalise": capitalise,
|
||||
"decapitalise": decapitalise,
|
||||
}
|
||||
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
|
||||
if err := tmpl.Execute(buffer, data); err != nil {
|
||||
@@ -129,11 +154,11 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||
return string(code), nil
|
||||
}
|
||||
// For all others just return as is for now
|
||||
return string(buffer.Bytes()), nil
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// bindType is a set of type binders that convert Solidity types to some supported
|
||||
// programming language.
|
||||
// programming language types.
|
||||
var bindType = map[Lang]func(kind abi.Type) string{
|
||||
LangGo: bindTypeGo,
|
||||
LangJava: bindTypeJava,
|
||||
@@ -254,6 +279,33 @@ func bindTypeJava(kind abi.Type) string {
|
||||
}
|
||||
}
|
||||
|
||||
// bindTopicType is a set of type binders that convert Solidity types to some
|
||||
// supported programming language topic types.
|
||||
var bindTopicType = map[Lang]func(kind abi.Type) string{
|
||||
LangGo: bindTopicTypeGo,
|
||||
LangJava: bindTopicTypeJava,
|
||||
}
|
||||
|
||||
// bindTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
||||
// funcionality as for simple types, but dynamic types get converted to hashes.
|
||||
func bindTopicTypeGo(kind abi.Type) string {
|
||||
bound := bindTypeGo(kind)
|
||||
if bound == "string" || bound == "[]byte" {
|
||||
bound = "common.Hash"
|
||||
}
|
||||
return bound
|
||||
}
|
||||
|
||||
// bindTypeGo converts a Solidity topic type to a Java one. It is almost the same
|
||||
// funcionality as for simple types, but dynamic types get converted to hashes.
|
||||
func bindTopicTypeJava(kind abi.Type) string {
|
||||
bound := bindTypeJava(kind)
|
||||
if bound == "String" || bound == "Bytes" {
|
||||
bound = "Hash"
|
||||
}
|
||||
return bound
|
||||
}
|
||||
|
||||
// namedType is a set of functions that transform language specific types to
|
||||
// named versions that my be used inside method names.
|
||||
var namedType = map[Lang]func(string, abi.Type) string{
|
||||
@@ -304,8 +356,15 @@ var methodNormalizer = map[Lang]func(string) string{
|
||||
LangJava: decapitalise,
|
||||
}
|
||||
|
||||
// capitalise makes the first character of a string upper case.
|
||||
// capitalise makes the first character of a string upper case, also removing any
|
||||
// prefixing underscores from the variable names.
|
||||
func capitalise(input string) string {
|
||||
for len(input) > 0 && input[0] == '_' {
|
||||
input = input[1:]
|
||||
}
|
||||
if len(input) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.ToUpper(input[:1]) + input[1:]
|
||||
}
|
||||
|
||||
@@ -314,16 +373,25 @@ func decapitalise(input string) string {
|
||||
return strings.ToLower(input[:1]) + input[1:]
|
||||
}
|
||||
|
||||
// structured checks whether a method has enough information to return a proper
|
||||
// Go struct ot if flat returns are needed.
|
||||
func structured(method abi.Method) bool {
|
||||
if len(method.Outputs) < 2 {
|
||||
// structured checks whether a list of ABI data types has enough information to
|
||||
// operate through a proper Go struct or if flat returns are needed.
|
||||
func structured(args abi.Arguments) bool {
|
||||
if len(args) < 2 {
|
||||
return false
|
||||
}
|
||||
for _, out := range method.Outputs {
|
||||
exists := make(map[string]bool)
|
||||
for _, out := range args {
|
||||
// If the name is anonymous, we can't organize into a struct
|
||||
if out.Name == "" {
|
||||
return false
|
||||
}
|
||||
// If the field name is empty when normalized or collides (var, Var, _var, _Var),
|
||||
// we can't organize into a struct
|
||||
field := capitalise(out.Name)
|
||||
if field == "" || exists[field] {
|
||||
return false
|
||||
}
|
||||
exists[field] = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ var bindTests = []struct {
|
||||
{"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]},
|
||||
{"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]},
|
||||
{"type":"function","name":"namedOutputs","constant":true,"inputs":[],"outputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}]},
|
||||
{"type":"function","name":"collidingOutputs","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"},{"name":"Str","type":"string"}]},
|
||||
{"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]},
|
||||
{"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]}
|
||||
]
|
||||
@@ -140,12 +141,71 @@ var bindTests = []struct {
|
||||
str1, err = b.NamedOutput(nil)
|
||||
str1, err = b.AnonOutput(nil)
|
||||
res, _ := b.NamedOutputs(nil)
|
||||
str1, str2, err = b.CollidingOutputs(nil)
|
||||
str1, str2, err = b.AnonOutputs(nil)
|
||||
str1, str2, err = b.MixedOutputs(nil)
|
||||
|
||||
fmt.Println(str1, str2, res.Str1, res.Str2, err)
|
||||
}`,
|
||||
},
|
||||
// Tests that named, anonymous and indexed events are handled correctly
|
||||
{
|
||||
`EventChecker`, ``, ``,
|
||||
`
|
||||
[
|
||||
{"type":"event","name":"empty","inputs":[]},
|
||||
{"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256","indexed":true}]},
|
||||
{"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int256"}]},
|
||||
{"type":"event","name":"anonymous","anonymous":true,"inputs":[]},
|
||||
{"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]}
|
||||
]
|
||||
`,
|
||||
`if e, err := NewEventChecker(common.Address{}, nil); e == nil || err != nil {
|
||||
t.Fatalf("binding (%v) nil or error (%v) not nil", e, nil)
|
||||
} else if false { // Don't run, just compile and test types
|
||||
var (
|
||||
err error
|
||||
res bool
|
||||
str string
|
||||
dat []byte
|
||||
hash common.Hash
|
||||
)
|
||||
_, err = e.FilterEmpty(nil)
|
||||
_, err = e.FilterIndexed(nil, []common.Address{}, []*big.Int{})
|
||||
|
||||
mit, err := e.FilterMixed(nil, []common.Address{})
|
||||
|
||||
res = mit.Next() // Make sure the iterator has a Next method
|
||||
err = mit.Error() // Make sure the iterator has an Error method
|
||||
err = mit.Close() // Make sure the iterator has a Close method
|
||||
|
||||
fmt.Println(mit.Event.Raw.BlockHash) // Make sure the raw log is contained within the results
|
||||
fmt.Println(mit.Event.Num) // Make sure the unpacked non-indexed fields are present
|
||||
fmt.Println(mit.Event.Addr) // Make sure the reconstructed indexed fields are present
|
||||
|
||||
dit, err := e.FilterDynamic(nil, []string{}, [][]byte{})
|
||||
|
||||
str = dit.Event.Str // Make sure non-indexed strings retain their type
|
||||
dat = dit.Event.Dat // Make sure non-indexed bytes retain their type
|
||||
hash = dit.Event.IdxStr // Make sure indexed strings turn into hashes
|
||||
hash = dit.Event.IdxDat // Make sure indexed bytes turn into hashes
|
||||
|
||||
sink := make(chan *EventCheckerMixed)
|
||||
sub, err := e.WatchMixed(nil, sink, []common.Address{})
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
event := <-sink
|
||||
fmt.Println(event.Raw.BlockHash) // Make sure the raw log is contained within the results
|
||||
fmt.Println(event.Num) // Make sure the unpacked non-indexed fields are present
|
||||
fmt.Println(event.Addr) // Make sure the reconstructed indexed fields are present
|
||||
|
||||
fmt.Println(res, str, dat, hash, err)
|
||||
}
|
||||
// Run a tiny reflection test to ensure disallowed methods don't appear
|
||||
if _, ok := reflect.TypeOf(&EventChecker{}).MethodByName("FilterAnonymous"); ok {
|
||||
t.Errorf("binding has disallowed method (FilterAnonymous)")
|
||||
}`,
|
||||
},
|
||||
// Test that contract interactions (deploy, transact and call) generate working code
|
||||
{
|
||||
`Interactor`,
|
||||
@@ -397,7 +457,6 @@ var bindTests = []struct {
|
||||
sim.Commit()
|
||||
|
||||
// Set the field with automatic estimation and check that it succeeds
|
||||
auth.GasLimit = nil
|
||||
if _, err := limiter.SetField(auth, "automatic"); err != nil {
|
||||
t.Fatalf("Failed to call automatically gased transaction: %v", err)
|
||||
}
|
||||
@@ -447,6 +506,237 @@ var bindTests = []struct {
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
`Underscorer`,
|
||||
`
|
||||
contract Underscorer {
|
||||
function UnderscoredOutput() constant returns (int _int, string _string) {
|
||||
return (314, "pi");
|
||||
}
|
||||
function LowerLowerCollision() constant returns (int _res, int res) {
|
||||
return (1, 2);
|
||||
}
|
||||
function LowerUpperCollision() constant returns (int _res, int Res) {
|
||||
return (1, 2);
|
||||
}
|
||||
function UpperLowerCollision() constant returns (int _Res, int res) {
|
||||
return (1, 2);
|
||||
}
|
||||
function UpperUpperCollision() constant returns (int _Res, int Res) {
|
||||
return (1, 2);
|
||||
}
|
||||
function PurelyUnderscoredOutput() constant returns (int _, int res) {
|
||||
return (1, 2);
|
||||
}
|
||||
function AllPurelyUnderscoredOutput() constant returns (int _, int __) {
|
||||
return (1, 2);
|
||||
}
|
||||
}
|
||||
`, `6060604052341561000f57600080fd5b6103498061001e6000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806303a592131461008857806367e6633d146100b85780639df484851461014d578063af7486ab1461017d578063b564b34d146101ad578063e02ab24d146101dd578063e409ca451461020d575b600080fd5b341561009357600080fd5b61009b61023d565b604051808381526020018281526020019250505060405180910390f35b34156100c357600080fd5b6100cb610252565b6040518083815260200180602001828103825283818151815260200191508051906020019080838360005b838110156101115780820151818401526020810190506100f6565b50505050905090810190601f16801561013e5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b341561015857600080fd5b6101606102a0565b604051808381526020018281526020019250505060405180910390f35b341561018857600080fd5b6101906102b5565b604051808381526020018281526020019250505060405180910390f35b34156101b857600080fd5b6101c06102ca565b604051808381526020018281526020019250505060405180910390f35b34156101e857600080fd5b6101f06102df565b604051808381526020018281526020019250505060405180910390f35b341561021857600080fd5b6102206102f4565b604051808381526020018281526020019250505060405180910390f35b60008060016002819150809050915091509091565b600061025c610309565b61013a8090506040805190810160405280600281526020017f7069000000000000000000000000000000000000000000000000000000000000815250915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b60008060016002819150809050915091509091565b6020604051908101604052806000815250905600a165627a7a72305820c11dcfa136fc7d182ee4d34f0b12d988496228f7e2d02d2b5376d996ca1743d00029`,
|
||||
`[{"constant":true,"inputs":[],"name":"LowerUpperCollision","outputs":[{"name":"_res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UnderscoredOutput","outputs":[{"name":"_int","type":"int256"},{"name":"_string","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperLowerCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"AllPurelyUnderscoredOutput","outputs":[{"name":"_","type":"int256"},{"name":"__","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"UpperUpperCollision","outputs":[{"name":"_Res","type":"int256"},{"name":"Res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"LowerLowerCollision","outputs":[{"name":"_res","type":"int256"},{"name":"res","type":"int256"}],"payable":false,"stateMutability":"view","type":"function"}]`,
|
||||
`
|
||||
// Generate a new random account and a funded simulator
|
||||
key, _ := crypto.GenerateKey()
|
||||
auth := bind.NewKeyedTransactor(key)
|
||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}})
|
||||
|
||||
// Deploy a underscorer tester contract and execute a structured call on it
|
||||
_, _, underscorer, err := DeployUnderscorer(auth, sim)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deploy underscorer contract: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
// Verify that underscored return values correctly parse into structs
|
||||
if res, err := underscorer.UnderscoredOutput(nil); err != nil {
|
||||
t.Errorf("Failed to call constant function: %v", err)
|
||||
} else if res.Int.Cmp(big.NewInt(314)) != 0 || res.String != "pi" {
|
||||
t.Errorf("Invalid result, want: {314, \"pi\"}, got: %+v", res)
|
||||
}
|
||||
// Verify that underscored and non-underscored name collisions force tuple outputs
|
||||
var a, b *big.Int
|
||||
|
||||
a, b, _ = underscorer.LowerLowerCollision(nil)
|
||||
a, b, _ = underscorer.LowerUpperCollision(nil)
|
||||
a, b, _ = underscorer.UpperLowerCollision(nil)
|
||||
a, b, _ = underscorer.UpperUpperCollision(nil)
|
||||
a, b, _ = underscorer.PurelyUnderscoredOutput(nil)
|
||||
a, b, _ = underscorer.AllPurelyUnderscoredOutput(nil)
|
||||
|
||||
fmt.Println(a, b, err)
|
||||
`,
|
||||
},
|
||||
// Tests that logs can be successfully filtered and decoded.
|
||||
{
|
||||
`Eventer`,
|
||||
`
|
||||
contract Eventer {
|
||||
event SimpleEvent (
|
||||
address indexed Addr,
|
||||
bytes32 indexed Id,
|
||||
bool indexed Flag,
|
||||
uint Value
|
||||
);
|
||||
function raiseSimpleEvent(address addr, bytes32 id, bool flag, uint value) {
|
||||
SimpleEvent(addr, id, flag, value);
|
||||
}
|
||||
|
||||
event NodataEvent (
|
||||
uint indexed Number,
|
||||
int16 indexed Short,
|
||||
uint32 indexed Long
|
||||
);
|
||||
function raiseNodataEvent(uint number, int16 short, uint32 long) {
|
||||
NodataEvent(number, short, long);
|
||||
}
|
||||
|
||||
event DynamicEvent (
|
||||
string indexed IndexedString,
|
||||
bytes indexed IndexedBytes,
|
||||
string NonIndexedString,
|
||||
bytes NonIndexedBytes
|
||||
);
|
||||
function raiseDynamicEvent(string str, bytes blob) {
|
||||
DynamicEvent(str, blob, str, blob);
|
||||
}
|
||||
}
|
||||
`,
|
||||
`6060604052341561000f57600080fd5b61042c8061001e6000396000f300606060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063528300ff1461005c578063630c31e2146100fc578063c7d116dd14610156575b600080fd5b341561006757600080fd5b6100fa600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190803590602001908201803590602001908080601f01602080910402602001604051908101604052809392919081815260200183838082843782019150505050505091905050610194565b005b341561010757600080fd5b610154600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035600019169060200190919080351515906020019091908035906020019091905050610367565b005b341561016157600080fd5b610192600480803590602001909190803560010b90602001909190803563ffffffff169060200190919050506103c3565b005b806040518082805190602001908083835b6020831015156101ca57805182526020820191506020810190506020830392506101a5565b6001836020036101000a0380198251168184511680821785525050505050509050019150506040518091039020826040518082805190602001908083835b60208310151561022d5780518252602082019150602081019050602083039250610208565b6001836020036101000a03801982511681845116808217855250505050505090500191505060405180910390207f3281fd4f5e152dd3385df49104a3f633706e21c9e80672e88d3bcddf33101f008484604051808060200180602001838103835285818151815260200191508051906020019080838360005b838110156102c15780820151818401526020810190506102a6565b50505050905090810190601f1680156102ee5780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b8381101561032757808201518184015260208101905061030c565b50505050905090810190601f1680156103545780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a35050565b81151583600019168573ffffffffffffffffffffffffffffffffffffffff167f1f097de4289df643bd9c11011cc61367aa12983405c021056e706eb5ba1250c8846040518082815260200191505060405180910390a450505050565b8063ffffffff168260010b847f3ca7f3a77e5e6e15e781850bc82e32adfa378a2a609370db24b4d0fae10da2c960405160405180910390a45050505600a165627a7a72305820d1f8a8bbddbc5bb29f285891d6ae1eef8420c52afdc05e1573f6114d8e1714710029`,
|
||||
`[{"constant":false,"inputs":[{"name":"str","type":"string"},{"name":"blob","type":"bytes"}],"name":"raiseDynamicEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"},{"name":"id","type":"bytes32"},{"name":"flag","type":"bool"},{"name":"value","type":"uint256"}],"name":"raiseSimpleEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"number","type":"uint256"},{"name":"short","type":"int16"},{"name":"long","type":"uint32"}],"name":"raiseNodataEvent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Addr","type":"address"},{"indexed":true,"name":"Id","type":"bytes32"},{"indexed":true,"name":"Flag","type":"bool"},{"indexed":false,"name":"Value","type":"uint256"}],"name":"SimpleEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"Number","type":"uint256"},{"indexed":true,"name":"Short","type":"int16"},{"indexed":true,"name":"Long","type":"uint32"}],"name":"NodataEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"IndexedString","type":"string"},{"indexed":true,"name":"IndexedBytes","type":"bytes"},{"indexed":false,"name":"NonIndexedString","type":"string"},{"indexed":false,"name":"NonIndexedBytes","type":"bytes"}],"name":"DynamicEvent","type":"event"}]`,
|
||||
`
|
||||
// Generate a new random account and a funded simulator
|
||||
key, _ := crypto.GenerateKey()
|
||||
auth := bind.NewKeyedTransactor(key)
|
||||
sim := backends.NewSimulatedBackend(core.GenesisAlloc{auth.From: {Balance: big.NewInt(10000000000)}})
|
||||
|
||||
// Deploy an eventer contract
|
||||
_, _, eventer, err := DeployEventer(auth, sim)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deploy eventer contract: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
// Inject a few events into the contract, gradually more in each block
|
||||
for i := 1; i <= 3; i++ {
|
||||
for j := 1; j <= i; j++ {
|
||||
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{byte(j)}, [32]byte{byte(j)}, true, big.NewInt(int64(10*i+j))); err != nil {
|
||||
t.Fatalf("block %d, event %d: raise failed: %v", i, j, err)
|
||||
}
|
||||
}
|
||||
sim.Commit()
|
||||
}
|
||||
// Test filtering for certain events and ensure they can be found
|
||||
sit, err := eventer.FilterSimpleEvent(nil, []common.Address{common.Address{1}, common.Address{3}}, [][32]byte{{byte(1)}, {byte(2)}, {byte(3)}}, []bool{true})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to filter for simple events: %v", err)
|
||||
}
|
||||
defer sit.Close()
|
||||
|
||||
sit.Next()
|
||||
if sit.Event.Value.Uint64() != 11 || !sit.Event.Flag {
|
||||
t.Errorf("simple log content mismatch: have %v, want {11, true}", sit.Event)
|
||||
}
|
||||
sit.Next()
|
||||
if sit.Event.Value.Uint64() != 21 || !sit.Event.Flag {
|
||||
t.Errorf("simple log content mismatch: have %v, want {21, true}", sit.Event)
|
||||
}
|
||||
sit.Next()
|
||||
if sit.Event.Value.Uint64() != 31 || !sit.Event.Flag {
|
||||
t.Errorf("simple log content mismatch: have %v, want {31, true}", sit.Event)
|
||||
}
|
||||
sit.Next()
|
||||
if sit.Event.Value.Uint64() != 33 || !sit.Event.Flag {
|
||||
t.Errorf("simple log content mismatch: have %v, want {33, true}", sit.Event)
|
||||
}
|
||||
|
||||
if sit.Next() {
|
||||
t.Errorf("unexpected simple event found: %+v", sit.Event)
|
||||
}
|
||||
if err = sit.Error(); err != nil {
|
||||
t.Fatalf("simple event iteration failed: %v", err)
|
||||
}
|
||||
// Test raising and filtering for an event with no data component
|
||||
if _, err := eventer.RaiseNodataEvent(auth, big.NewInt(314), 141, 271); err != nil {
|
||||
t.Fatalf("failed to raise nodata event: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
nit, err := eventer.FilterNodataEvent(nil, []*big.Int{big.NewInt(314)}, []int16{140, 141, 142}, []uint32{271})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to filter for nodata events: %v", err)
|
||||
}
|
||||
defer nit.Close()
|
||||
|
||||
if !nit.Next() {
|
||||
t.Fatalf("nodata log not found: %v", nit.Error())
|
||||
}
|
||||
if nit.Event.Number.Uint64() != 314 {
|
||||
t.Errorf("nodata log content mismatch: have %v, want 314", nit.Event.Number)
|
||||
}
|
||||
if nit.Next() {
|
||||
t.Errorf("unexpected nodata event found: %+v", nit.Event)
|
||||
}
|
||||
if err = nit.Error(); err != nil {
|
||||
t.Fatalf("nodata event iteration failed: %v", err)
|
||||
}
|
||||
// Test raising and filtering for events with dynamic indexed components
|
||||
if _, err := eventer.RaiseDynamicEvent(auth, "Hello", []byte("World")); err != nil {
|
||||
t.Fatalf("failed to raise dynamic event: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
dit, err := eventer.FilterDynamicEvent(nil, []string{"Hi", "Hello", "Bye"}, [][]byte{[]byte("World")})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to filter for dynamic events: %v", err)
|
||||
}
|
||||
defer dit.Close()
|
||||
|
||||
if !dit.Next() {
|
||||
t.Fatalf("dynamic log not found: %v", dit.Error())
|
||||
}
|
||||
if dit.Event.NonIndexedString != "Hello" || string(dit.Event.NonIndexedBytes) != "World" || dit.Event.IndexedString != common.HexToHash("0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2") || dit.Event.IndexedBytes != common.HexToHash("0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18") {
|
||||
t.Errorf("dynamic log content mismatch: have %v, want {'0x06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2, '0xf2208c967df089f60420785795c0a9ba8896b0f6f1867fa7f1f12ad6f79c1a18', 'Hello', 'World'}", dit.Event)
|
||||
}
|
||||
if dit.Next() {
|
||||
t.Errorf("unexpected dynamic event found: %+v", dit.Event)
|
||||
}
|
||||
if err = dit.Error(); err != nil {
|
||||
t.Fatalf("dynamic event iteration failed: %v", err)
|
||||
}
|
||||
// Test subscribing to an event and raising it afterwards
|
||||
ch := make(chan *EventerSimpleEvent, 16)
|
||||
sub, err := eventer.WatchSimpleEvent(nil, ch, nil, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to subscribe to simple events: %v", err)
|
||||
}
|
||||
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{255}, [32]byte{255}, true, big.NewInt(255)); err != nil {
|
||||
t.Fatalf("failed to raise subscribed simple event: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
select {
|
||||
case event := <-ch:
|
||||
if event.Value.Uint64() != 255 {
|
||||
t.Errorf("simple log content mismatch: have %v, want 255", event)
|
||||
}
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
t.Fatalf("subscribed simple event didn't arrive")
|
||||
}
|
||||
// Unsubscribe from the event and make sure we're not delivered more
|
||||
sub.Unsubscribe()
|
||||
|
||||
if _, err := eventer.RaiseSimpleEvent(auth, common.Address{254}, [32]byte{254}, true, big.NewInt(254)); err != nil {
|
||||
t.Fatalf("failed to raise subscribed simple event: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
select {
|
||||
case event := <-ch:
|
||||
t.Fatalf("unsubscribed simple event arrived: %v", event)
|
||||
case <-time.After(250 * time.Millisecond):
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
// Tests that packages generated by the binder can be successfully compiled and
|
||||
@@ -498,7 +788,7 @@ func TestBindings(t *testing.T) {
|
||||
}
|
||||
}
|
||||
// Test the entire package and report any failures
|
||||
cmd := exec.Command(gocmd, "test", "-v")
|
||||
cmd := exec.Command(gocmd, "test", "-v", "-count", "1")
|
||||
cmd.Dir = pkg
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
t.Fatalf("failed to run binding test: %v\n%s", err, out)
|
||||
|
||||
@@ -32,6 +32,7 @@ type tmplContract struct {
|
||||
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||
Events map[string]*tmplEvent // Contract events accessors
|
||||
}
|
||||
|
||||
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||
@@ -39,7 +40,13 @@ type tmplContract struct {
|
||||
type tmplMethod struct {
|
||||
Original abi.Method // Original method as parsed by the abi package
|
||||
Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns)
|
||||
Structured bool // Whether the returns should be accumulated into a contract
|
||||
Structured bool // Whether the returns should be accumulated into a struct
|
||||
}
|
||||
|
||||
// tmplEvent is a wrapper around an a
|
||||
type tmplEvent struct {
|
||||
Original abi.Event // Original event as parsed by the abi package
|
||||
Normalized abi.Event // Normalized version of the parsed fields
|
||||
}
|
||||
|
||||
// tmplSource is language to template mapping containing all the supported
|
||||
@@ -75,7 +82,7 @@ package {{.Package}}
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract} }, nil
|
||||
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
@@ -83,6 +90,7 @@ package {{.Package}}
|
||||
type {{.Type}} struct {
|
||||
{{.Type}}Caller // Read-only binding to the contract
|
||||
{{.Type}}Transactor // Write-only binding to the contract
|
||||
{{.Type}}Filterer // Log filterer for contract events
|
||||
}
|
||||
|
||||
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||
@@ -95,6 +103,11 @@ package {{.Package}}
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||
type {{.Type}}Filterer struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||
// with pre-set call and transact options.
|
||||
type {{.Type}}Session struct {
|
||||
@@ -134,16 +147,16 @@ package {{.Package}}
|
||||
|
||||
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
||||
contract, err := bind{{.Type}}(address, backend, backend)
|
||||
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract} }, nil
|
||||
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||
}
|
||||
|
||||
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
||||
contract, err := bind{{.Type}}(address, caller, nil)
|
||||
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -152,20 +165,29 @@ package {{.Package}}
|
||||
|
||||
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
||||
contract, err := bind{{.Type}}(address, nil, transactor)
|
||||
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}Transactor{contract: contract}, nil
|
||||
}
|
||||
|
||||
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
||||
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
||||
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{.Type}}Filterer{contract: contract}, nil
|
||||
}
|
||||
|
||||
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) {
|
||||
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
||||
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bind.NewBoundContract(address, parsed, caller, transactor), nil
|
||||
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
@@ -263,6 +285,137 @@ package {{.Package}}
|
||||
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range .Events}}
|
||||
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
||||
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
||||
Event *{{$contract.Type}}{{.Normalized.Name}} // Event containing the contract specifics and raw log
|
||||
|
||||
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
||||
event string // Event name to use for unpacking event data
|
||||
|
||||
logs chan types.Log // Log channel receiving the found contract events
|
||||
sub ethereum.Subscription // Subscription for errors, completion and termination
|
||||
done bool // Whether the subscription completed delivering logs
|
||||
fail error // Occurred error to stop iteration
|
||||
}
|
||||
// Next advances the iterator to the subsequent event, returning whether there
|
||||
// are any more events found. In case of a retrieval or parsing error, false is
|
||||
// returned and Error() can be queried for the exact failure.
|
||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Next() bool {
|
||||
// If the iterator failed, stop iterating
|
||||
if (it.fail != nil) {
|
||||
return false
|
||||
}
|
||||
// If the iterator completed, deliver directly whatever's available
|
||||
if (it.done) {
|
||||
select {
|
||||
case log := <-it.logs:
|
||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||
it.fail = err
|
||||
return false
|
||||
}
|
||||
it.Event.Raw = log
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Iterator still in progress, wait for either a data or an error event
|
||||
select {
|
||||
case log := <-it.logs:
|
||||
it.Event = new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||
it.fail = err
|
||||
return false
|
||||
}
|
||||
it.Event.Raw = log
|
||||
return true
|
||||
|
||||
case err := <-it.sub.Err():
|
||||
it.done = true
|
||||
it.fail = err
|
||||
return it.Next()
|
||||
}
|
||||
}
|
||||
// Error returns any retrieval or parsing error occurred during filtering.
|
||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
||||
return it.fail
|
||||
}
|
||||
// Close terminates the iteration process, releasing any pending underlying
|
||||
// resources.
|
||||
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
||||
it.sub.Unsubscribe()
|
||||
return nil
|
||||
}
|
||||
|
||||
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type}}{{else}}{{bindtype .Type}}{{end}}; {{end}}
|
||||
Raw types.Log // Blockchain specific contextual infos
|
||||
}
|
||||
|
||||
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
||||
{{range .Normalized.Inputs}}
|
||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||
for _, {{.Name}}Item := range {{.Name}} {
|
||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||
}{{end}}{{end}}
|
||||
|
||||
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
||||
}
|
||||
|
||||
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.Id}}.
|
||||
//
|
||||
// Solidity: {{.Original.String}}
|
||||
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type}}{{end}}{{end}}) (event.Subscription, error) {
|
||||
{{range .Normalized.Inputs}}
|
||||
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||
for _, {{.Name}}Item := range {{.Name}} {
|
||||
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||
}{{end}}{{end}}
|
||||
|
||||
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case log := <-logs:
|
||||
// New log arrived, parse the event and forward to the user
|
||||
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||
return err
|
||||
}
|
||||
event.Raw = log
|
||||
|
||||
select {
|
||||
case sink <- event:
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
|
||||
189
accounts/abi/bind/topics.go
Normal file
189
accounts/abi/bind/topics.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package bind
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// makeTopics converts a filter query argument list into a filter topic set.
|
||||
func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
||||
topics := make([][]common.Hash, len(query))
|
||||
for i, filter := range query {
|
||||
for _, rule := range filter {
|
||||
var topic common.Hash
|
||||
|
||||
// Try to generate the topic based on simple types
|
||||
switch rule := rule.(type) {
|
||||
case common.Hash:
|
||||
copy(topic[:], rule[:])
|
||||
case common.Address:
|
||||
copy(topic[common.HashLength-common.AddressLength:], rule[:])
|
||||
case *big.Int:
|
||||
blob := rule.Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case bool:
|
||||
if rule {
|
||||
topic[common.HashLength-1] = 1
|
||||
}
|
||||
case int8:
|
||||
blob := big.NewInt(int64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case int16:
|
||||
blob := big.NewInt(int64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case int32:
|
||||
blob := big.NewInt(int64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case int64:
|
||||
blob := big.NewInt(rule).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint8:
|
||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint16:
|
||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint32:
|
||||
blob := new(big.Int).SetUint64(uint64(rule)).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case uint64:
|
||||
blob := new(big.Int).SetUint64(rule).Bytes()
|
||||
copy(topic[common.HashLength-len(blob):], blob)
|
||||
case string:
|
||||
hash := crypto.Keccak256Hash([]byte(rule))
|
||||
copy(topic[:], hash[:])
|
||||
case []byte:
|
||||
hash := crypto.Keccak256Hash(rule)
|
||||
copy(topic[:], hash[:])
|
||||
|
||||
default:
|
||||
// Attempt to generate the topic from funky types
|
||||
val := reflect.ValueOf(rule)
|
||||
|
||||
switch {
|
||||
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
|
||||
reflect.Copy(reflect.ValueOf(topic[common.HashLength-val.Len():]), val)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
|
||||
}
|
||||
}
|
||||
topics[i] = append(topics[i], topic)
|
||||
}
|
||||
}
|
||||
return topics, nil
|
||||
}
|
||||
|
||||
// Big batch of reflect types for topic reconstruction.
|
||||
var (
|
||||
reflectHash = reflect.TypeOf(common.Hash{})
|
||||
reflectAddress = reflect.TypeOf(common.Address{})
|
||||
reflectBigInt = reflect.TypeOf(new(big.Int))
|
||||
)
|
||||
|
||||
// parseTopics converts the indexed topic fields into actual log field values.
|
||||
//
|
||||
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
|
||||
// hashes as the topic value!
|
||||
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
|
||||
// Sanity check that the fields and topics match up
|
||||
if len(fields) != len(topics) {
|
||||
return errors.New("topic/field count mismatch")
|
||||
}
|
||||
// Iterate over all the fields and reconstruct them from topics
|
||||
for _, arg := range fields {
|
||||
if !arg.Indexed {
|
||||
return errors.New("non-indexed field in topic reconstruction")
|
||||
}
|
||||
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
|
||||
|
||||
// Try to parse the topic back into the fields based on primitive types
|
||||
switch field.Kind() {
|
||||
case reflect.Bool:
|
||||
if topics[0][common.HashLength-1] == 1 {
|
||||
field.Set(reflect.ValueOf(true))
|
||||
}
|
||||
case reflect.Int8:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(int8(num.Int64())))
|
||||
|
||||
case reflect.Int16:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(int16(num.Int64())))
|
||||
|
||||
case reflect.Int32:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(int32(num.Int64())))
|
||||
|
||||
case reflect.Int64:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(num.Int64()))
|
||||
|
||||
case reflect.Uint8:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(uint8(num.Uint64())))
|
||||
|
||||
case reflect.Uint16:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(uint16(num.Uint64())))
|
||||
|
||||
case reflect.Uint32:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(uint32(num.Uint64())))
|
||||
|
||||
case reflect.Uint64:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(num.Uint64()))
|
||||
|
||||
default:
|
||||
// Ran out of plain primitive types, try custom types
|
||||
switch field.Type() {
|
||||
case reflectHash: // Also covers all dynamic types
|
||||
field.Set(reflect.ValueOf(topics[0]))
|
||||
|
||||
case reflectAddress:
|
||||
var addr common.Address
|
||||
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
|
||||
field.Set(reflect.ValueOf(addr))
|
||||
|
||||
case reflectBigInt:
|
||||
num := new(big.Int).SetBytes(topics[0][:])
|
||||
field.Set(reflect.ValueOf(num))
|
||||
|
||||
default:
|
||||
// Ran out of custom types, try the crazies
|
||||
switch {
|
||||
case arg.Type.T == abi.FixedBytesTy:
|
||||
reflect.Copy(field, reflect.ValueOf(topics[0][common.HashLength-arg.Type.Size:]))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
topics = topics[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -34,18 +34,18 @@ var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d
|
||||
|
||||
var waitDeployedTests = map[string]struct {
|
||||
code string
|
||||
gas *big.Int
|
||||
gas uint64
|
||||
wantAddress common.Address
|
||||
wantErr error
|
||||
}{
|
||||
"successful deploy": {
|
||||
code: `6060604052600a8060106000396000f360606040526008565b00`,
|
||||
gas: big.NewInt(3000000),
|
||||
gas: 3000000,
|
||||
wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"),
|
||||
},
|
||||
"empty code": {
|
||||
code: ``,
|
||||
gas: big.NewInt(300000),
|
||||
gas: 300000,
|
||||
wantErr: bind.ErrNoCodeAfterDeploy,
|
||||
wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"),
|
||||
},
|
||||
|
||||
@@ -18,7 +18,6 @@ package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -31,7 +30,18 @@ import (
|
||||
type Event struct {
|
||||
Name string
|
||||
Anonymous bool
|
||||
Inputs []Argument
|
||||
Inputs Arguments
|
||||
}
|
||||
|
||||
func (event Event) String() string {
|
||||
inputs := make([]string, len(event.Inputs))
|
||||
for i, input := range event.Inputs {
|
||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
||||
if input.Indexed {
|
||||
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
|
||||
}
|
||||
|
||||
// Id returns the canonical representation of the event's signature used by the
|
||||
@@ -45,93 +55,3 @@ func (e Event) Id() common.Hash {
|
||||
}
|
||||
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
||||
}
|
||||
|
||||
// unpacks an event return tuple into a struct of corresponding go types
|
||||
//
|
||||
// Unpacking can be done into a struct or a slice/array.
|
||||
func (e Event) tupleUnpack(v interface{}, output []byte) error {
|
||||
// make sure the passed value is a pointer
|
||||
valueOf := reflect.ValueOf(v)
|
||||
if reflect.Ptr != valueOf.Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
|
||||
var (
|
||||
value = valueOf.Elem()
|
||||
typ = value.Type()
|
||||
)
|
||||
|
||||
if value.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
||||
}
|
||||
|
||||
j := 0
|
||||
for i := 0; i < len(e.Inputs); i++ {
|
||||
input := e.Inputs[i]
|
||||
if input.Indexed {
|
||||
// can't read, continue
|
||||
continue
|
||||
} else if input.Type.T == ArrayTy {
|
||||
// need to move this up because they read sequentially
|
||||
j += input.Type.Size
|
||||
}
|
||||
marshalledValue, err := toGoType((i+j)*32, input.Type, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reflectValue := reflect.ValueOf(marshalledValue)
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
for j := 0; j < typ.NumField(); j++ {
|
||||
field := typ.Field(j)
|
||||
// TODO read tags: `abi:"fieldName"`
|
||||
if field.Name == strings.ToUpper(e.Inputs[i].Name[:1])+e.Inputs[i].Name[1:] {
|
||||
if err := set(value.Field(j), reflectValue, e.Inputs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if value.Len() < i {
|
||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(e.Inputs), value.Len())
|
||||
}
|
||||
v := value.Index(i)
|
||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
|
||||
}
|
||||
reflectValue := reflect.ValueOf(marshalledValue)
|
||||
if err := set(v.Elem(), reflectValue, e.Inputs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Event) isTupleReturn() bool { return len(e.Inputs) > 1 }
|
||||
|
||||
func (e Event) singleUnpack(v interface{}, output []byte) error {
|
||||
// make sure the passed value is a pointer
|
||||
valueOf := reflect.ValueOf(v)
|
||||
if reflect.Ptr != valueOf.Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
|
||||
if e.Inputs[0].Indexed {
|
||||
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
||||
}
|
||||
|
||||
value := valueOf.Elem()
|
||||
|
||||
marshalledValue, err := toGoType(0, e.Inputs[0].Type, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := set(value, reflect.ValueOf(marshalledValue), e.Inputs[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,13 +17,53 @@
|
||||
package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var jsonEventTransfer = []byte(`{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true, "name": "from", "type": "address"
|
||||
}, {
|
||||
"indexed": true, "name": "to", "type": "address"
|
||||
}, {
|
||||
"indexed": false, "name": "value", "type": "uint256"
|
||||
}],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}`)
|
||||
|
||||
var jsonEventPledge = []byte(`{
|
||||
"anonymous": false,
|
||||
"inputs": [{
|
||||
"indexed": false, "name": "who", "type": "address"
|
||||
}, {
|
||||
"indexed": false, "name": "wad", "type": "uint128"
|
||||
}, {
|
||||
"indexed": false, "name": "currency", "type": "bytes3"
|
||||
}],
|
||||
"name": "Pledge",
|
||||
"type": "event"
|
||||
}`)
|
||||
|
||||
// 1000000
|
||||
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||
|
||||
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
func TestEventId(t *testing.T) {
|
||||
var table = []struct {
|
||||
definition string
|
||||
@@ -54,3 +94,223 @@ func TestEventId(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||
type testStruct struct {
|
||||
Value1 [2]uint8
|
||||
Value2 uint8
|
||||
}
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
require.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
var i uint8 = 1
|
||||
for ; i <= 3; i++ {
|
||||
b.Write(packNum(reflect.ValueOf(i)))
|
||||
}
|
||||
var rst testStruct
|
||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||
require.Equal(t, [2]uint8{1, 2}, rst.Value1)
|
||||
require.Equal(t, uint8(3), rst.Value2)
|
||||
}
|
||||
|
||||
func TestEventTupleUnpack(t *testing.T) {
|
||||
|
||||
type EventTransfer struct {
|
||||
Value *big.Int
|
||||
}
|
||||
|
||||
type EventPledge struct {
|
||||
Who common.Address
|
||||
Wad *big.Int
|
||||
Currency [3]byte
|
||||
}
|
||||
|
||||
type BadEventPledge struct {
|
||||
Who string
|
||||
Wad int
|
||||
Currency [3]byte
|
||||
}
|
||||
|
||||
bigint := new(big.Int)
|
||||
bigintExpected := big.NewInt(1000000)
|
||||
bigintExpected2 := big.NewInt(2218516807680)
|
||||
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||
var testCases = []struct {
|
||||
data string
|
||||
dest interface{}
|
||||
expected interface{}
|
||||
jsonLog []byte
|
||||
error string
|
||||
name string
|
||||
}{{
|
||||
transferData1,
|
||||
&EventTransfer{},
|
||||
&EventTransfer{Value: bigintExpected},
|
||||
jsonEventTransfer,
|
||||
"",
|
||||
"Can unpack ERC20 Transfer event into structure",
|
||||
}, {
|
||||
transferData1,
|
||||
&[]interface{}{&bigint},
|
||||
&[]interface{}{&bigintExpected},
|
||||
jsonEventTransfer,
|
||||
"",
|
||||
"Can unpack ERC20 Transfer event into slice",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&EventPledge{},
|
||||
&EventPledge{
|
||||
addr,
|
||||
bigintExpected2,
|
||||
[3]byte{'u', 's', 'd'}},
|
||||
jsonEventPledge,
|
||||
"",
|
||||
"Can unpack Pledge event into structure",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||
&[]interface{}{
|
||||
&addr,
|
||||
&bigintExpected2,
|
||||
&[3]byte{'u', 's', 'd'}},
|
||||
jsonEventPledge,
|
||||
"",
|
||||
"Can unpack Pledge event into slice",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[3]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||
&[3]interface{}{
|
||||
&addr,
|
||||
&bigintExpected2,
|
||||
&[3]byte{'u', 's', 'd'}},
|
||||
jsonEventPledge,
|
||||
"",
|
||||
"Can unpack Pledge event into an array",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[]interface{}{new(int), 0, 0},
|
||||
&[]interface{}{},
|
||||
jsonEventPledge,
|
||||
"abi: cannot unmarshal common.Address in to int",
|
||||
"Can not unpack Pledge event into slice with wrong types",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&BadEventPledge{},
|
||||
&BadEventPledge{},
|
||||
jsonEventPledge,
|
||||
"abi: cannot unmarshal common.Address in to string",
|
||||
"Can not unpack Pledge event into struct with wrong filed types",
|
||||
}, {
|
||||
pledgeData1,
|
||||
&[]interface{}{common.Address{}, new(big.Int)},
|
||||
&[]interface{}{},
|
||||
jsonEventPledge,
|
||||
"abi: insufficient number of elements in the list/array for unpack, want 3, got 2",
|
||||
"Can not unpack Pledge event into too short slice",
|
||||
}, {
|
||||
pledgeData1,
|
||||
new(map[string]interface{}),
|
||||
&[]interface{}{},
|
||||
jsonEventPledge,
|
||||
"abi: cannot unmarshal tuple into map[string]interface {}",
|
||||
"Can not unpack Pledge event into map",
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert := assert.New(t)
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
||||
if tc.error == "" {
|
||||
assert.Nil(err, "Should be able to unpack event data.")
|
||||
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||
} else {
|
||||
assert.EqualError(err, tc.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error {
|
||||
data, err := hex.DecodeString(hexData)
|
||||
assert.NoError(err, "Hex data should be a correct hex-string")
|
||||
var e Event
|
||||
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
|
||||
a := ABI{Events: map[string]Event{"e": e}}
|
||||
return a.Unpack(dest, "e", data)
|
||||
}
|
||||
|
||||
/*
|
||||
Taken from
|
||||
https://github.com/ethereum/go-ethereum/pull/15568
|
||||
*/
|
||||
|
||||
type testResult struct {
|
||||
Values [2]*big.Int
|
||||
Value1 *big.Int
|
||||
Value2 *big.Int
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
definition string
|
||||
want testResult
|
||||
}
|
||||
|
||||
func (tc testCase) encoded(intType, arrayType Type) []byte {
|
||||
var b bytes.Buffer
|
||||
if tc.want.Value1 != nil {
|
||||
val, _ := intType.pack(reflect.ValueOf(tc.want.Value1))
|
||||
b.Write(val)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.want.Values, [2]*big.Int{nil, nil}) {
|
||||
val, _ := arrayType.pack(reflect.ValueOf(tc.want.Values))
|
||||
b.Write(val)
|
||||
}
|
||||
if tc.want.Value2 != nil {
|
||||
val, _ := intType.pack(reflect.ValueOf(tc.want.Value2))
|
||||
b.Write(val)
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
||||
func TestEventUnpackIndexed(t *testing.T) {
|
||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||
type testStruct struct {
|
||||
Value1 uint8
|
||||
Value2 uint8
|
||||
}
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
require.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
b.Write(packNum(reflect.ValueOf(uint8(8))))
|
||||
var rst testStruct
|
||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||
require.Equal(t, uint8(0), rst.Value1)
|
||||
require.Equal(t, uint8(8), rst.Value2)
|
||||
}
|
||||
|
||||
// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input.
|
||||
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
||||
type testStruct struct {
|
||||
Value1 [2]uint8
|
||||
Value2 string
|
||||
}
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
require.NoError(t, err)
|
||||
var b bytes.Buffer
|
||||
stringOut := "abc"
|
||||
// number of fields that will be encoded * 32
|
||||
b.Write(packNum(reflect.ValueOf(32)))
|
||||
b.Write(packNum(reflect.ValueOf(len(stringOut))))
|
||||
b.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||
|
||||
var rst testStruct
|
||||
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
|
||||
require.Equal(t, stringOut, rst.Value2)
|
||||
}
|
||||
|
||||
@@ -18,13 +18,12 @@ package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// Callable method given a `Name` and whether the method is a constant.
|
||||
// Method represents a callable given a `Name` and whether the method is a constant.
|
||||
// If the method is `Const` no transaction needs to be created for this
|
||||
// particular Method call. It can easily be simulated using a local VM.
|
||||
// For example a `Balance()` method only needs to retrieve something
|
||||
@@ -35,125 +34,8 @@ import (
|
||||
type Method struct {
|
||||
Name string
|
||||
Const bool
|
||||
Inputs []Argument
|
||||
Outputs []Argument
|
||||
}
|
||||
|
||||
func (method Method) pack(args ...interface{}) ([]byte, error) {
|
||||
// Make sure arguments match up and pack them
|
||||
if len(args) != len(method.Inputs) {
|
||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
|
||||
}
|
||||
// variable input is the output appended at the end of packed
|
||||
// output. This is used for strings and bytes types input.
|
||||
var variableInput []byte
|
||||
|
||||
var ret []byte
|
||||
for i, a := range args {
|
||||
input := method.Inputs[i]
|
||||
// pack the input
|
||||
packed, err := input.Type.pack(reflect.ValueOf(a))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("`%s` %v", method.Name, err)
|
||||
}
|
||||
|
||||
// check for a slice type (string, bytes, slice)
|
||||
if input.Type.requiresLengthPrefix() {
|
||||
// calculate the offset
|
||||
offset := len(method.Inputs)*32 + len(variableInput)
|
||||
// set the offset
|
||||
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||
// Append the packed output to the variable input. The variable input
|
||||
// will be appended at the end of the input.
|
||||
variableInput = append(variableInput, packed...)
|
||||
} else {
|
||||
// append the packed value to the input
|
||||
ret = append(ret, packed...)
|
||||
}
|
||||
}
|
||||
// append the variable input at the end of the packed input
|
||||
ret = append(ret, variableInput...)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// unpacks a method return tuple into a struct of corresponding go types
|
||||
//
|
||||
// Unpacking can be done into a struct or a slice/array.
|
||||
func (method Method) tupleUnpack(v interface{}, output []byte) error {
|
||||
// make sure the passed value is a pointer
|
||||
valueOf := reflect.ValueOf(v)
|
||||
if reflect.Ptr != valueOf.Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
|
||||
var (
|
||||
value = valueOf.Elem()
|
||||
typ = value.Type()
|
||||
)
|
||||
|
||||
j := 0
|
||||
for i := 0; i < len(method.Outputs); i++ {
|
||||
toUnpack := method.Outputs[i]
|
||||
if toUnpack.Type.T == ArrayTy {
|
||||
// need to move this up because they read sequentially
|
||||
j += toUnpack.Type.Size
|
||||
}
|
||||
marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reflectValue := reflect.ValueOf(marshalledValue)
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.Struct:
|
||||
for j := 0; j < typ.NumField(); j++ {
|
||||
field := typ.Field(j)
|
||||
// TODO read tags: `abi:"fieldName"`
|
||||
if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
|
||||
if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if value.Len() < i {
|
||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(method.Outputs), value.Len())
|
||||
}
|
||||
v := value.Index(i)
|
||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
|
||||
}
|
||||
reflectValue := reflect.ValueOf(marshalledValue)
|
||||
if err := set(v.Elem(), reflectValue, method.Outputs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 }
|
||||
|
||||
func (method Method) singleUnpack(v interface{}, output []byte) error {
|
||||
// make sure the passed value is a pointer
|
||||
valueOf := reflect.ValueOf(v)
|
||||
if reflect.Ptr != valueOf.Kind() {
|
||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||
}
|
||||
|
||||
value := valueOf.Elem()
|
||||
|
||||
marshalledValue, err := toGoType(0, method.Outputs[0].Type, output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
Inputs Arguments
|
||||
Outputs Arguments
|
||||
}
|
||||
|
||||
// Sig returns the methods string signature according to the ABI spec.
|
||||
@@ -163,35 +45,35 @@ func (method Method) singleUnpack(v interface{}, output []byte) error {
|
||||
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||
//
|
||||
// Please note that "int" is substitute for its canonical representation "int256"
|
||||
func (m Method) Sig() string {
|
||||
types := make([]string, len(m.Inputs))
|
||||
func (method Method) Sig() string {
|
||||
types := make([]string, len(method.Inputs))
|
||||
i := 0
|
||||
for _, input := range m.Inputs {
|
||||
for _, input := range method.Inputs {
|
||||
types[i] = input.Type.String()
|
||||
i++
|
||||
}
|
||||
return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ","))
|
||||
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
||||
}
|
||||
|
||||
func (m Method) String() string {
|
||||
inputs := make([]string, len(m.Inputs))
|
||||
for i, input := range m.Inputs {
|
||||
func (method Method) String() string {
|
||||
inputs := make([]string, len(method.Inputs))
|
||||
for i, input := range method.Inputs {
|
||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
||||
}
|
||||
outputs := make([]string, len(m.Outputs))
|
||||
for i, output := range m.Outputs {
|
||||
outputs := make([]string, len(method.Outputs))
|
||||
for i, output := range method.Outputs {
|
||||
if len(output.Name) > 0 {
|
||||
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
||||
}
|
||||
outputs[i] += output.Type.String()
|
||||
}
|
||||
constant := ""
|
||||
if m.Const {
|
||||
if method.Const {
|
||||
constant = "constant "
|
||||
}
|
||||
return fmt.Sprintf("function %v(%v) %sreturns(%v)", m.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||
}
|
||||
|
||||
func (m Method) Id() []byte {
|
||||
return crypto.Keccak256([]byte(m.Sig()))[:4]
|
||||
func (method Method) Id() []byte {
|
||||
return crypto.Keccak256([]byte(method.Sig()))[:4]
|
||||
}
|
||||
|
||||
@@ -48,9 +48,8 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||
case BoolTy:
|
||||
if reflectValue.Bool() {
|
||||
return math.PaddedBigBytes(common.Big1, 32)
|
||||
} else {
|
||||
return math.PaddedBigBytes(common.Big0, 32)
|
||||
}
|
||||
return math.PaddedBigBytes(common.Big0, 32)
|
||||
case BytesTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -85,3 +85,28 @@ func set(dst, src reflect.Value, output Argument) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// requireAssignable assures that `dest` is a pointer and it's not an interface.
|
||||
func requireAssignable(dst, src reflect.Value) error {
|
||||
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
|
||||
return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// requireUnpackKind verifies preconditions for unpacking `args` into `kind`
|
||||
func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
|
||||
args Arguments) error {
|
||||
|
||||
switch k {
|
||||
case reflect.Struct:
|
||||
case reflect.Slice, reflect.Array:
|
||||
if minLen := args.LengthNonIndexed(); v.Len() < minLen {
|
||||
return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d",
|
||||
minLen, v.Len())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Type enumerator
|
||||
const (
|
||||
IntTy byte = iota
|
||||
UintTy
|
||||
@@ -100,69 +101,66 @@ func NewType(t string) (typ Type, err error) {
|
||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||
}
|
||||
return typ, err
|
||||
} else {
|
||||
// parse the type and size of the abi-type.
|
||||
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||
// varSize is the size of the variable
|
||||
var varSize int
|
||||
if len(parsedType[3]) > 0 {
|
||||
var err error
|
||||
varSize, err = strconv.Atoi(parsedType[2])
|
||||
if err != nil {
|
||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||
}
|
||||
} else {
|
||||
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||
// this should fail because it means that there's something wrong with
|
||||
// the abi type (the compiler should always format it to the size...always)
|
||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||
}
|
||||
}
|
||||
// parse the type and size of the abi-type.
|
||||
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||
// varSize is the size of the variable
|
||||
var varSize int
|
||||
if len(parsedType[3]) > 0 {
|
||||
var err error
|
||||
varSize, err = strconv.Atoi(parsedType[2])
|
||||
if err != nil {
|
||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||
}
|
||||
// varType is the parsed abi type
|
||||
varType := parsedType[1]
|
||||
|
||||
switch varType {
|
||||
case "int":
|
||||
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
||||
typ.Size = varSize
|
||||
typ.T = IntTy
|
||||
case "uint":
|
||||
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
||||
typ.Size = varSize
|
||||
typ.T = UintTy
|
||||
case "bool":
|
||||
typ.Kind = reflect.Bool
|
||||
typ.T = BoolTy
|
||||
typ.Type = reflect.TypeOf(bool(false))
|
||||
case "address":
|
||||
typ.Kind = reflect.Array
|
||||
typ.Type = address_t
|
||||
typ.Size = 20
|
||||
typ.T = AddressTy
|
||||
case "string":
|
||||
typ.Kind = reflect.String
|
||||
typ.Type = reflect.TypeOf("")
|
||||
typ.T = StringTy
|
||||
case "bytes":
|
||||
if varSize == 0 {
|
||||
typ.T = BytesTy
|
||||
typ.Kind = reflect.Slice
|
||||
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||
} else {
|
||||
typ.T = FixedBytesTy
|
||||
typ.Kind = reflect.Array
|
||||
typ.Size = varSize
|
||||
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||
}
|
||||
case "function":
|
||||
typ.Kind = reflect.Array
|
||||
typ.T = FunctionTy
|
||||
typ.Size = 24
|
||||
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
||||
default:
|
||||
} else {
|
||||
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||
// this should fail because it means that there's something wrong with
|
||||
// the abi type (the compiler should always format it to the size...always)
|
||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||
}
|
||||
}
|
||||
// varType is the parsed abi type
|
||||
switch varType := parsedType[1]; varType {
|
||||
case "int":
|
||||
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
||||
typ.Size = varSize
|
||||
typ.T = IntTy
|
||||
case "uint":
|
||||
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
||||
typ.Size = varSize
|
||||
typ.T = UintTy
|
||||
case "bool":
|
||||
typ.Kind = reflect.Bool
|
||||
typ.T = BoolTy
|
||||
typ.Type = reflect.TypeOf(bool(false))
|
||||
case "address":
|
||||
typ.Kind = reflect.Array
|
||||
typ.Type = address_t
|
||||
typ.Size = 20
|
||||
typ.T = AddressTy
|
||||
case "string":
|
||||
typ.Kind = reflect.String
|
||||
typ.Type = reflect.TypeOf("")
|
||||
typ.T = StringTy
|
||||
case "bytes":
|
||||
if varSize == 0 {
|
||||
typ.T = BytesTy
|
||||
typ.Kind = reflect.Slice
|
||||
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||
} else {
|
||||
typ.T = FixedBytesTy
|
||||
typ.Kind = reflect.Array
|
||||
typ.Size = varSize
|
||||
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||
}
|
||||
case "function":
|
||||
typ.Kind = reflect.Array
|
||||
typ.T = FunctionTy
|
||||
typ.Size = 24
|
||||
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
||||
default:
|
||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
@@ -25,15 +25,6 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// unpacker is a utility interface that enables us to have
|
||||
// abstraction between events and methods and also to properly
|
||||
// "unpack" them; e.g. events use Inputs, methods use Outputs.
|
||||
type unpacker interface {
|
||||
tupleUnpack(v interface{}, output []byte) error
|
||||
singleUnpack(v interface{}, output []byte) error
|
||||
isTupleReturn() bool
|
||||
}
|
||||
|
||||
// reads the integer based on its kind
|
||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||
switch kind {
|
||||
@@ -79,7 +70,7 @@ func readBool(word []byte) (bool, error) {
|
||||
// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
||||
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||
if t.T != FunctionTy {
|
||||
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array.")
|
||||
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
|
||||
}
|
||||
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||
@@ -92,7 +83,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||
// through reflection, creates a fixed array to be read from
|
||||
func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||
if t.T != FixedBytesTy {
|
||||
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array.")
|
||||
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
|
||||
}
|
||||
// convert
|
||||
array := reflect.New(t.Type).Elem()
|
||||
@@ -203,14 +194,3 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
|
||||
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
|
||||
return
|
||||
}
|
||||
|
||||
// checks for proper formatting of byte output
|
||||
func bytesAreProper(output []byte) error {
|
||||
if len(output) == 0 {
|
||||
return fmt.Errorf("abi: unmarshalling empty output")
|
||||
} else if len(output)%32 != 0 {
|
||||
return fmt.Errorf("abi: improperly formatted output")
|
||||
} else {
|
||||
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.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
@@ -22,10 +22,12 @@ import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type unpackTest struct {
|
||||
@@ -257,82 +259,179 @@ var unpackTests = []unpackTest{
|
||||
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
|
||||
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
|
||||
},
|
||||
// struct outputs
|
||||
{
|
||||
def: `[{"name":"int1","type":"int256"},{"name":"int2","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{big.NewInt(1), big.NewInt(2)},
|
||||
},
|
||||
{
|
||||
def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: multiple outputs mapping to the same struct field 'Int'",
|
||||
},
|
||||
{
|
||||
def: `[{"name":"int","type":"int256"},{"name":"_int","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: multiple outputs mapping to the same struct field 'Int'",
|
||||
},
|
||||
{
|
||||
def: `[{"name":"Int","type":"int256"},{"name":"_int","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: multiple outputs mapping to the same struct field 'Int'",
|
||||
},
|
||||
{
|
||||
def: `[{"name":"Int","type":"int256"},{"name":"_","type":"int256"}]`,
|
||||
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
|
||||
want: struct {
|
||||
Int1 *big.Int
|
||||
Int2 *big.Int
|
||||
}{},
|
||||
err: "abi: purely underscored output cannot unpack to struct",
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnpack(t *testing.T) {
|
||||
for i, test := range unpackTests {
|
||||
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
||||
abi, err := JSON(strings.NewReader(def))
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ABI definition %s: %v", def, err)
|
||||
}
|
||||
encb, err := hex.DecodeString(test.enc)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid hex: %s" + test.enc)
|
||||
}
|
||||
outptr := reflect.New(reflect.TypeOf(test.want))
|
||||
err = abi.Unpack(outptr.Interface(), "method", encb)
|
||||
if err := test.checkError(err); err != nil {
|
||||
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
|
||||
continue
|
||||
}
|
||||
out := outptr.Elem().Interface()
|
||||
if !reflect.DeepEqual(test.want, out) {
|
||||
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
|
||||
}
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
||||
abi, err := JSON(strings.NewReader(def))
|
||||
if err != nil {
|
||||
t.Fatalf("invalid ABI definition %s: %v", def, err)
|
||||
}
|
||||
encb, err := hex.DecodeString(test.enc)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid hex: %s" + test.enc)
|
||||
}
|
||||
outptr := reflect.New(reflect.TypeOf(test.want))
|
||||
err = abi.Unpack(outptr.Interface(), "method", encb)
|
||||
if err := test.checkError(err); err != nil {
|
||||
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
|
||||
return
|
||||
}
|
||||
out := outptr.Elem().Interface()
|
||||
if !reflect.DeepEqual(test.want, out) {
|
||||
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiReturnWithStruct(t *testing.T) {
|
||||
type methodMultiOutput struct {
|
||||
Int *big.Int
|
||||
String string
|
||||
}
|
||||
|
||||
func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
|
||||
const definition = `[
|
||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||
var expected = methodMultiOutput{big.NewInt(1), "hello"}
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
// using buff to make the code readable
|
||||
buff := new(bytes.Buffer)
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||
stringOut := "hello"
|
||||
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||
buff.Write(common.RightPadBytes([]byte(expected.String), 32))
|
||||
return abi, buff.Bytes(), expected
|
||||
}
|
||||
|
||||
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 {
|
||||
func TestMethodMultiReturn(t *testing.T) {
|
||||
type reversed struct {
|
||||
String string
|
||||
Int *big.Int
|
||||
}
|
||||
|
||||
err = abi.Unpack(&reversed, "multi", buff.Bytes())
|
||||
abi, data, expected := methodMultiReturn(require.New(t))
|
||||
bigint := new(big.Int)
|
||||
var testCases = []struct {
|
||||
dest interface{}
|
||||
expected interface{}
|
||||
error string
|
||||
name string
|
||||
}{{
|
||||
&methodMultiOutput{},
|
||||
&expected,
|
||||
"",
|
||||
"Can unpack into structure",
|
||||
}, {
|
||||
&reversed{},
|
||||
&reversed{expected.String, expected.Int},
|
||||
"",
|
||||
"Can unpack into reversed structure",
|
||||
}, {
|
||||
&[]interface{}{&bigint, new(string)},
|
||||
&[]interface{}{&expected.Int, &expected.String},
|
||||
"",
|
||||
"Can unpack into a slice",
|
||||
}, {
|
||||
&[2]interface{}{&bigint, new(string)},
|
||||
&[2]interface{}{&expected.Int, &expected.String},
|
||||
"",
|
||||
"Can unpack into an array",
|
||||
}, {
|
||||
&[]interface{}{new(int), new(int)},
|
||||
&[]interface{}{&expected.Int, &expected.String},
|
||||
"abi: cannot unmarshal *big.Int in to int",
|
||||
"Can not unpack into a slice with wrong types",
|
||||
}, {
|
||||
&[]interface{}{new(int)},
|
||||
&[]interface{}{},
|
||||
"abi: insufficient number of elements in the list/array for unpack, want 2, got 1",
|
||||
"Can not unpack into a slice with wrong types",
|
||||
}}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
err := abi.Unpack(tc.dest, "multi", data)
|
||||
if tc.error == "" {
|
||||
require.Nil(err, "Should be able to unpack method outputs.")
|
||||
require.Equal(tc.expected, tc.dest)
|
||||
} else {
|
||||
require.EqualError(err, tc.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiReturnWithArray(t *testing.T) {
|
||||
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
buff := new(bytes.Buffer)
|
||||
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009"))
|
||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008"))
|
||||
|
||||
if reversed.Int == nil || reversed.Int.Cmp(big.NewInt(1)) != 0 {
|
||||
t.Error("expected Int to be 1 got", reversed.Int)
|
||||
ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9}
|
||||
ret2, ret2Exp := new(uint64), uint64(8)
|
||||
if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if reversed.String != stringOut {
|
||||
t.Error("expected String to be", stringOut, "got", reversed.String)
|
||||
if !reflect.DeepEqual(*ret1, ret1Exp) {
|
||||
t.Error("array result", *ret1, "!= Expected", ret1Exp)
|
||||
}
|
||||
if *ret2 != ret2Exp {
|
||||
t.Error("int result", *ret2, "!= Expected", ret2Exp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,11 +467,11 @@ func TestUnmarshal(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if bytes.Compare(p0, p0Exp) != 0 {
|
||||
if !bytes.Equal(p0, p0Exp) {
|
||||
t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0)
|
||||
}
|
||||
|
||||
if bytes.Compare(p1[:], p1Exp) != 0 {
|
||||
if !bytes.Equal(p1[:], p1Exp) {
|
||||
t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func NewAuthNeededError(needed string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the standard error interfacel.
|
||||
// Error implements the standard error interface.
|
||||
func (err *AuthNeededError) Error() string {
|
||||
return fmt.Sprintf("authentication needed: %s", err.Needed)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestWatchNewFile(t *testing.T) {
|
||||
|
||||
// Ensure the watcher is started before adding any files.
|
||||
ks.Accounts()
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Move in the files.
|
||||
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
|
||||
@@ -349,6 +349,9 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Now replace file contents
|
||||
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -362,6 +365,9 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Now replace file contents again
|
||||
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -374,6 +380,10 @@ func TestUpdatedKeyfileContents(t *testing.T) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// needed so that modTime of `file` is different to its current value after ioutil.WriteFile
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
|
||||
// Now replace file contents with crap
|
||||
if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -58,6 +58,9 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid hex in encSeed")
|
||||
}
|
||||
if len(encSeedBytes) < 16 {
|
||||
return nil, errors.New("invalid encSeed, too short")
|
||||
}
|
||||
iv := encSeedBytes[:16]
|
||||
cipherText := encSeedBytes[16:]
|
||||
/*
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
|
||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
/**
|
||||
* Messages for TREZOR communication
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
||||
|
||||
//go:generate protoc --go_out=Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor,import_path=trezor:. types.proto messages.proto
|
||||
//go:generate protoc --go_out=import_path=trezor:. types.proto messages.proto
|
||||
|
||||
// Package trezor contains the wire protocol wrapper in Go.
|
||||
package trezor
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/types.proto
|
||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
/**
|
||||
* Types for TREZOR communication
|
||||
*
|
||||
|
||||
@@ -180,7 +180,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||
AddressN: derivationPath,
|
||||
Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(),
|
||||
GasPrice: tx.GasPrice().Bytes(),
|
||||
GasLimit: tx.Gas().Bytes(),
|
||||
GasLimit: new(big.Int).SetUint64(tx.Gas()).Bytes(),
|
||||
Value: tx.Value().Bytes(),
|
||||
DataLength: &length,
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ environment:
|
||||
install:
|
||||
- git submodule update --init
|
||||
- rmdir C:\go /s /q
|
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.9.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.2.windows-%GETH_ARCH%.zip
|
||||
- 7z x go1.9.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||
- go version
|
||||
- gcc --version
|
||||
|
||||
|
||||
@@ -260,8 +260,7 @@ func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree {
|
||||
for d := 1; d <= depth(segmentCount); d++ {
|
||||
nodes := make([]*Node, count)
|
||||
for i := 0; i < len(nodes); i++ {
|
||||
var parent *Node
|
||||
parent = prevlevel[i/2]
|
||||
parent := prevlevel[i/2]
|
||||
t := NewNode(level, i, parent)
|
||||
nodes[i] = t
|
||||
}
|
||||
|
||||
62
build/ci.go
62
build/ci.go
@@ -19,11 +19,11 @@
|
||||
/*
|
||||
The ci command is called from Continuous Integration scripts.
|
||||
|
||||
Usage: go run ci.go <command> <command flags/arguments>
|
||||
Usage: go run build/ci.go <command> <command flags/arguments>
|
||||
|
||||
Available commands are:
|
||||
|
||||
install [ -arch architecture ] [ packages... ] -- builds packages and executables
|
||||
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
||||
test [ -coverage ] [ packages... ] -- runs the tests
|
||||
lint -- runs certain pre-selected linters
|
||||
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts
|
||||
@@ -121,7 +121,8 @@ var (
|
||||
// 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: yakkety is unsupported because it was officially deprecated on lanchpad.
|
||||
debDistros = []string{"trusty", "xenial", "zesty", "artful"}
|
||||
// Note: zesty is unsupported because it was officially deprecated on lanchpad.
|
||||
debDistros = []string{"trusty", "xenial", "artful", "bionic"}
|
||||
)
|
||||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
@@ -173,17 +174,24 @@ func main() {
|
||||
func doInstall(cmdline []string) {
|
||||
var (
|
||||
arch = flag.String("arch", "", "Architecture to cross build for")
|
||||
cc = flag.String("cc", "", "C compiler to cross build with")
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
env := build.Env()
|
||||
|
||||
// Check Go version. People regularly open issues about compilation
|
||||
// failure with outdated Go. This should save them the trouble.
|
||||
if runtime.Version() < "go1.7" && !strings.Contains(runtime.Version(), "devel") {
|
||||
log.Println("You have Go version", runtime.Version())
|
||||
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.")
|
||||
os.Exit(1)
|
||||
if !strings.Contains(runtime.Version(), "devel") {
|
||||
// Figure out the minor version number since we can't textually compare (1.10 < 1.7)
|
||||
var minor int
|
||||
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
||||
|
||||
if minor < 7 {
|
||||
log.Println("You have Go version", runtime.Version())
|
||||
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.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
// Compile packages given as arguments, or everything if there are no arguments.
|
||||
packages := []string{"./..."}
|
||||
@@ -199,7 +207,7 @@ func doInstall(cmdline []string) {
|
||||
build.MustRun(goinstall)
|
||||
return
|
||||
}
|
||||
// If we are cross compiling to ARMv5 ARMv6 or ARMv7, clean any prvious builds
|
||||
// If we are cross compiling to ARMv5 ARMv6 or ARMv7, clean any previous builds
|
||||
if *arch == "arm" {
|
||||
os.RemoveAll(filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_arm"))
|
||||
for _, path := range filepath.SplitList(build.GOPATH()) {
|
||||
@@ -207,7 +215,7 @@ func doInstall(cmdline []string) {
|
||||
}
|
||||
}
|
||||
// Seems we are cross compiling, work around forbidden GOBIN
|
||||
goinstall := goToolArch(*arch, "install", buildFlags(env)...)
|
||||
goinstall := goToolArch(*arch, *cc, "install", buildFlags(env)...)
|
||||
goinstall.Args = append(goinstall.Args, "-v")
|
||||
goinstall.Args = append(goinstall.Args, []string{"-buildmode", "archive"}...)
|
||||
goinstall.Args = append(goinstall.Args, packages...)
|
||||
@@ -221,7 +229,7 @@ func doInstall(cmdline []string) {
|
||||
}
|
||||
for name := range pkgs {
|
||||
if name == "main" {
|
||||
gobuild := goToolArch(*arch, "build", buildFlags(env)...)
|
||||
gobuild := goToolArch(*arch, *cc, "build", buildFlags(env)...)
|
||||
gobuild.Args = append(gobuild.Args, "-v")
|
||||
gobuild.Args = append(gobuild.Args, []string{"-o", executablePath(cmd.Name())}...)
|
||||
gobuild.Args = append(gobuild.Args, "."+string(filepath.Separator)+filepath.Join("cmd", cmd.Name()))
|
||||
@@ -249,15 +257,18 @@ func buildFlags(env build.Environment) (flags []string) {
|
||||
}
|
||||
|
||||
func goTool(subcmd string, args ...string) *exec.Cmd {
|
||||
return goToolArch(runtime.GOARCH, subcmd, args...)
|
||||
return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...)
|
||||
}
|
||||
|
||||
func goToolArch(arch string, subcmd string, args ...string) *exec.Cmd {
|
||||
func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd {
|
||||
cmd := build.GoTool(subcmd, args...)
|
||||
if subcmd == "build" || subcmd == "install" || subcmd == "test" {
|
||||
// Go CGO has a Windows linker error prior to 1.8 (https://github.com/golang/go/issues/8756).
|
||||
// Work around issue by allowing multiple definitions for <1.8 builds.
|
||||
if runtime.GOOS == "windows" && runtime.Version() < "go1.8" {
|
||||
var minor int
|
||||
fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor)
|
||||
|
||||
if runtime.GOOS == "windows" && minor < 8 {
|
||||
cmd.Args = append(cmd.Args, []string{"-ldflags", "-extldflags -Wl,--allow-multiple-definition"}...)
|
||||
}
|
||||
}
|
||||
@@ -268,6 +279,9 @@ func goToolArch(arch string, subcmd string, args ...string) *exec.Cmd {
|
||||
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
|
||||
cmd.Env = append(cmd.Env, "GOARCH="+arch)
|
||||
}
|
||||
if cc != "" {
|
||||
cmd.Env = append(cmd.Env, "CC="+cc)
|
||||
}
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
|
||||
continue
|
||||
@@ -319,17 +333,25 @@ func doLint(cmdline []string) {
|
||||
packages = flag.CommandLine.Args()
|
||||
}
|
||||
// Get metalinter and install all supported linters
|
||||
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v1"))
|
||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), "--install")
|
||||
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v2"))
|
||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), "--install")
|
||||
|
||||
// Run fast linters batched together
|
||||
configs := []string{"--vendor", "--disable-all", "--enable=vet", "--enable=gofmt", "--enable=misspell"}
|
||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
|
||||
configs := []string{
|
||||
"--vendor",
|
||||
"--disable-all",
|
||||
"--enable=vet",
|
||||
"--enable=gofmt",
|
||||
"--enable=misspell",
|
||||
"--enable=goconst",
|
||||
"--min-occurrences=6", // for goconst
|
||||
}
|
||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
||||
|
||||
// Run slow linters one by one
|
||||
for _, linter := range []string{"unconvert"} {
|
||||
for _, linter := range []string{"unconvert", "gosimple"} {
|
||||
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
|
||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,10 +55,9 @@ var (
|
||||
"crypto/sha3/",
|
||||
"internal/jsre/deps",
|
||||
"log/",
|
||||
"common/bitutil/bitutil",
|
||||
// don't license generated files
|
||||
"contracts/chequebook/contract/",
|
||||
"contracts/ens/contract/",
|
||||
"contracts/release/contract.go",
|
||||
"contracts/chequebook/contract/code.go",
|
||||
}
|
||||
|
||||
// paths with this prefix are licensed as GPL. all other files are LGPL.
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
@@ -96,12 +97,37 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", *listenAddr)
|
||||
if err != nil {
|
||||
utils.Fatalf("-ResolveUDPAddr: %v", err)
|
||||
}
|
||||
conn, err := net.ListenUDP("udp", addr)
|
||||
if err != nil {
|
||||
utils.Fatalf("-ListenUDP: %v", err)
|
||||
}
|
||||
|
||||
realaddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
if natm != nil {
|
||||
if !realaddr.IP.IsLoopback() {
|
||||
go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery")
|
||||
}
|
||||
// TODO: react to external IP changes over time.
|
||||
if ext, err := natm.ExternalIP(); err == nil {
|
||||
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
|
||||
}
|
||||
}
|
||||
|
||||
if *runv5 {
|
||||
if _, err := discv5.ListenUDP(nodeKey, *listenAddr, natm, "", restrictList); err != nil {
|
||||
if _, err := discv5.ListenUDP(nodeKey, conn, realaddr, "", restrictList); err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
} else {
|
||||
if _, err := discover.ListenUDP(nodeKey, *listenAddr, natm, "", restrictList); err != nil {
|
||||
cfg := discover.Config{
|
||||
PrivateKey: nodeKey,
|
||||
AnnounceAddr: realaddr,
|
||||
NetRestrict: restrictList,
|
||||
}
|
||||
if _, err := discover.ListenUDP(conn, cfg); err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
41
cmd/ethkey/README.md
Normal file
41
cmd/ethkey/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
ethkey
|
||||
======
|
||||
|
||||
ethkey is a simple command-line tool for working with Ethereum keyfiles.
|
||||
|
||||
|
||||
# Usage
|
||||
|
||||
### `ethkey generate`
|
||||
|
||||
Generate a new keyfile.
|
||||
If you want to use an existing private key to use in the keyfile, it can be
|
||||
specified by setting `--privatekey` with the location of the file containing the
|
||||
private key.
|
||||
|
||||
|
||||
### `ethkey inspect <keyfile>`
|
||||
|
||||
Print various information about the keyfile.
|
||||
Private key information can be printed by using the `--private` flag;
|
||||
make sure to use this feature with great caution!
|
||||
|
||||
|
||||
### `ethkey sign <keyfile> <message/file>`
|
||||
|
||||
Sign the message with a keyfile.
|
||||
It is possible to refer to a file containing the message.
|
||||
|
||||
|
||||
### `ethkey verify <address> <signature> <message/file>`
|
||||
|
||||
Verify the signature of the message.
|
||||
It is possible to refer to a file containing the message.
|
||||
|
||||
|
||||
## Passphrases
|
||||
|
||||
For every command that uses a keyfile, you will be prompted to provide the
|
||||
passphrase for decrypting the keyfile. To avoid this message, it is possible
|
||||
to pass the passphrase by using the `--passphrase` flag pointing to a file that
|
||||
contains the passphrase.
|
||||
118
cmd/ethkey/generate.go
Normal file
118
cmd/ethkey/generate.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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 (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/pborman/uuid"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type outputGenerate struct {
|
||||
Address string
|
||||
AddressEIP55 string
|
||||
}
|
||||
|
||||
var commandGenerate = cli.Command{
|
||||
Name: "generate",
|
||||
Usage: "generate new keyfile",
|
||||
ArgsUsage: "[ <keyfile> ]",
|
||||
Description: `
|
||||
Generate a new keyfile.
|
||||
|
||||
If you want to encrypt an existing private key, it can be specified by setting
|
||||
--privatekey with the location of the file containing the private key.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
passphraseFlag,
|
||||
jsonFlag,
|
||||
cli.StringFlag{
|
||||
Name: "privatekey",
|
||||
Usage: "file containing a raw private key to encrypt",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
// Check if keyfile path given and make sure it doesn't already exist.
|
||||
keyfilepath := ctx.Args().First()
|
||||
if keyfilepath == "" {
|
||||
keyfilepath = defaultKeyfileName
|
||||
}
|
||||
if _, err := os.Stat(keyfilepath); err == nil {
|
||||
utils.Fatalf("Keyfile already exists at %s.", keyfilepath)
|
||||
} else if !os.IsNotExist(err) {
|
||||
utils.Fatalf("Error checking if keyfile exists: %v", err)
|
||||
}
|
||||
|
||||
var privateKey *ecdsa.PrivateKey
|
||||
var err error
|
||||
if file := ctx.String("privatekey"); file != "" {
|
||||
// Load private key from file.
|
||||
privateKey, err = crypto.LoadECDSA(file)
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't load private key: %v", err)
|
||||
}
|
||||
} else {
|
||||
// If not loaded, generate random.
|
||||
privateKey, err = crypto.GenerateKey()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate random private key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create the keyfile object with a random UUID.
|
||||
id := uuid.NewRandom()
|
||||
key := &keystore.Key{
|
||||
Id: id,
|
||||
Address: crypto.PubkeyToAddress(privateKey.PublicKey),
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
|
||||
// Encrypt key with passphrase.
|
||||
passphrase := getPassPhrase(ctx, true)
|
||||
keyjson, err := keystore.EncryptKey(key, passphrase, keystore.StandardScryptN, keystore.StandardScryptP)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error encrypting key: %v", err)
|
||||
}
|
||||
|
||||
// Store the file to disk.
|
||||
if err := os.MkdirAll(filepath.Dir(keyfilepath), 0700); err != nil {
|
||||
utils.Fatalf("Could not create directory %s", filepath.Dir(keyfilepath))
|
||||
}
|
||||
if err := ioutil.WriteFile(keyfilepath, keyjson, 0600); err != nil {
|
||||
utils.Fatalf("Failed to write keyfile to %s: %v", keyfilepath, err)
|
||||
}
|
||||
|
||||
// Output some information.
|
||||
out := outputGenerate{
|
||||
Address: key.Address.Hex(),
|
||||
}
|
||||
if ctx.Bool(jsonFlag.Name) {
|
||||
mustPrintJSON(out)
|
||||
} else {
|
||||
fmt.Println("Address:", out.Address)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
91
cmd/ethkey/inspect.go
Normal file
91
cmd/ethkey/inspect.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type outputInspect struct {
|
||||
Address string
|
||||
PublicKey string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
var commandInspect = cli.Command{
|
||||
Name: "inspect",
|
||||
Usage: "inspect a keyfile",
|
||||
ArgsUsage: "<keyfile>",
|
||||
Description: `
|
||||
Print various information about the keyfile.
|
||||
|
||||
Private key information can be printed by using the --private flag;
|
||||
make sure to use this feature with great caution!`,
|
||||
Flags: []cli.Flag{
|
||||
passphraseFlag,
|
||||
jsonFlag,
|
||||
cli.BoolFlag{
|
||||
Name: "private",
|
||||
Usage: "include the private key in the output",
|
||||
},
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
keyfilepath := ctx.Args().First()
|
||||
|
||||
// Read key from file.
|
||||
keyjson, err := ioutil.ReadFile(keyfilepath)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err)
|
||||
}
|
||||
|
||||
// Decrypt key with passphrase.
|
||||
passphrase := getPassPhrase(ctx, false)
|
||||
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error decrypting key: %v", err)
|
||||
}
|
||||
|
||||
// Output all relevant information we can retrieve.
|
||||
showPrivate := ctx.Bool("private")
|
||||
out := outputInspect{
|
||||
Address: key.Address.Hex(),
|
||||
PublicKey: hex.EncodeToString(
|
||||
crypto.FromECDSAPub(&key.PrivateKey.PublicKey)),
|
||||
}
|
||||
if showPrivate {
|
||||
out.PrivateKey = hex.EncodeToString(crypto.FromECDSA(key.PrivateKey))
|
||||
}
|
||||
|
||||
if ctx.Bool(jsonFlag.Name) {
|
||||
mustPrintJSON(out)
|
||||
} else {
|
||||
fmt.Println("Address: ", out.Address)
|
||||
fmt.Println("Public key: ", out.PublicKey)
|
||||
if showPrivate {
|
||||
fmt.Println("Private key: ", out.PrivateKey)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
67
cmd/ethkey/main.go
Normal file
67
cmd/ethkey/main.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// 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"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultKeyfileName = "keyfile.json"
|
||||
)
|
||||
|
||||
// Git SHA1 commit hash of the release (set via linker flags)
|
||||
var gitCommit = ""
|
||||
|
||||
var app *cli.App
|
||||
|
||||
func init() {
|
||||
app = utils.NewApp(gitCommit, "an Ethereum key manager")
|
||||
app.Commands = []cli.Command{
|
||||
commandGenerate,
|
||||
commandInspect,
|
||||
commandSignMessage,
|
||||
commandVerifyMessage,
|
||||
}
|
||||
}
|
||||
|
||||
// Commonly used command line flags.
|
||||
var (
|
||||
passphraseFlag = cli.StringFlag{
|
||||
Name: "passwordfile",
|
||||
Usage: "the file that contains the passphrase for the keyfile",
|
||||
}
|
||||
jsonFlag = cli.BoolFlag{
|
||||
Name: "json",
|
||||
Usage: "output JSON instead of human-readable format",
|
||||
}
|
||||
messageFlag = cli.StringFlag{
|
||||
Name: "message",
|
||||
Usage: "the file that contains the message to sign/verify",
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
159
cmd/ethkey/message.go
Normal file
159
cmd/ethkey/message.go
Normal file
@@ -0,0 +1,159 @@
|
||||
// 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/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type outputSign struct {
|
||||
Signature string
|
||||
}
|
||||
|
||||
var msgfileFlag = cli.StringFlag{
|
||||
Name: "msgfile",
|
||||
Usage: "file containing the message to sign/verify",
|
||||
}
|
||||
|
||||
var commandSignMessage = cli.Command{
|
||||
Name: "signmessage",
|
||||
Usage: "sign a message",
|
||||
ArgsUsage: "<keyfile> <message>",
|
||||
Description: `
|
||||
Sign the message with a keyfile.
|
||||
|
||||
To sign a message contained in a file, use the --msgfile flag.
|
||||
`,
|
||||
Flags: []cli.Flag{
|
||||
passphraseFlag,
|
||||
jsonFlag,
|
||||
msgfileFlag,
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
message := getMessage(ctx, 1)
|
||||
|
||||
// Load the keyfile.
|
||||
keyfilepath := ctx.Args().First()
|
||||
keyjson, err := ioutil.ReadFile(keyfilepath)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err)
|
||||
}
|
||||
|
||||
// Decrypt key with passphrase.
|
||||
passphrase := getPassPhrase(ctx, false)
|
||||
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error decrypting key: %v", err)
|
||||
}
|
||||
|
||||
signature, err := crypto.Sign(signHash(message), key.PrivateKey)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to sign message: %v", err)
|
||||
}
|
||||
out := outputSign{Signature: hex.EncodeToString(signature)}
|
||||
if ctx.Bool(jsonFlag.Name) {
|
||||
mustPrintJSON(out)
|
||||
} else {
|
||||
fmt.Println("Signature:", out.Signature)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
type outputVerify struct {
|
||||
Success bool
|
||||
RecoveredAddress string
|
||||
RecoveredPublicKey string
|
||||
}
|
||||
|
||||
var commandVerifyMessage = cli.Command{
|
||||
Name: "verifymessage",
|
||||
Usage: "verify the signature of a signed message",
|
||||
ArgsUsage: "<address> <signature> <message>",
|
||||
Description: `
|
||||
Verify the signature of the message.
|
||||
It is possible to refer to a file containing the message.`,
|
||||
Flags: []cli.Flag{
|
||||
jsonFlag,
|
||||
msgfileFlag,
|
||||
},
|
||||
Action: func(ctx *cli.Context) error {
|
||||
addressStr := ctx.Args().First()
|
||||
signatureHex := ctx.Args().Get(1)
|
||||
message := getMessage(ctx, 2)
|
||||
|
||||
if !common.IsHexAddress(addressStr) {
|
||||
utils.Fatalf("Invalid address: %s", addressStr)
|
||||
}
|
||||
address := common.HexToAddress(addressStr)
|
||||
signature, err := hex.DecodeString(signatureHex)
|
||||
if err != nil {
|
||||
utils.Fatalf("Signature encoding is not hexadecimal: %v", err)
|
||||
}
|
||||
|
||||
recoveredPubkey, err := crypto.SigToPub(signHash(message), signature)
|
||||
if err != nil || recoveredPubkey == nil {
|
||||
utils.Fatalf("Signature verification failed: %v", err)
|
||||
}
|
||||
recoveredPubkeyBytes := crypto.FromECDSAPub(recoveredPubkey)
|
||||
recoveredAddress := crypto.PubkeyToAddress(*recoveredPubkey)
|
||||
success := address == recoveredAddress
|
||||
|
||||
out := outputVerify{
|
||||
Success: success,
|
||||
RecoveredPublicKey: hex.EncodeToString(recoveredPubkeyBytes),
|
||||
RecoveredAddress: recoveredAddress.Hex(),
|
||||
}
|
||||
if ctx.Bool(jsonFlag.Name) {
|
||||
mustPrintJSON(out)
|
||||
} else {
|
||||
if out.Success {
|
||||
fmt.Println("Signature verification successful!")
|
||||
} else {
|
||||
fmt.Println("Signature verification failed!")
|
||||
}
|
||||
fmt.Println("Recovered public key:", out.RecoveredPublicKey)
|
||||
fmt.Println("Recovered address:", out.RecoveredAddress)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func getMessage(ctx *cli.Context, msgarg int) []byte {
|
||||
if file := ctx.String("msgfile"); file != "" {
|
||||
if len(ctx.Args()) > msgarg {
|
||||
utils.Fatalf("Can't use --msgfile and message argument at the same time.")
|
||||
}
|
||||
msg, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't read message file: %v", err)
|
||||
}
|
||||
return msg
|
||||
} else if len(ctx.Args()) == msgarg+1 {
|
||||
return []byte(ctx.Args().Get(msgarg))
|
||||
}
|
||||
utils.Fatalf("Invalid number of arguments: want %d, got %d", msgarg+1, len(ctx.Args()))
|
||||
return nil
|
||||
}
|
||||
70
cmd/ethkey/message_test.go
Normal file
70
cmd/ethkey/message_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMessageSignVerify(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "ethkey-test")
|
||||
if err != nil {
|
||||
t.Fatal("Can't create temporary directory:", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
keyfile := filepath.Join(tmpdir, "the-keyfile")
|
||||
message := "test message"
|
||||
|
||||
// Create the key.
|
||||
generate := runEthkey(t, "generate", keyfile)
|
||||
generate.Expect(`
|
||||
!! Unsupported terminal, password will be echoed.
|
||||
Passphrase: {{.InputLine "foobar"}}
|
||||
Repeat passphrase: {{.InputLine "foobar"}}
|
||||
`)
|
||||
_, matches := generate.ExpectRegexp(`Address: (0x[0-9a-fA-F]{40})\n`)
|
||||
address := matches[1]
|
||||
generate.ExpectExit()
|
||||
|
||||
// Sign a message.
|
||||
sign := runEthkey(t, "signmessage", keyfile, message)
|
||||
sign.Expect(`
|
||||
!! Unsupported terminal, password will be echoed.
|
||||
Passphrase: {{.InputLine "foobar"}}
|
||||
`)
|
||||
_, matches = sign.ExpectRegexp(`Signature: ([0-9a-f]+)\n`)
|
||||
signature := matches[1]
|
||||
sign.ExpectExit()
|
||||
|
||||
// Verify the message.
|
||||
verify := runEthkey(t, "verifymessage", address, signature, message)
|
||||
_, matches = verify.ExpectRegexp(`
|
||||
Signature verification successful!
|
||||
Recovered public key: [0-9a-f]+
|
||||
Recovered address: (0x[0-9a-fA-F]{40})
|
||||
`)
|
||||
recovered := matches[1]
|
||||
verify.ExpectExit()
|
||||
|
||||
if recovered != address {
|
||||
t.Error("recovered address doesn't match generated key")
|
||||
}
|
||||
}
|
||||
54
cmd/ethkey/run_test.go
Normal file
54
cmd/ethkey/run_test.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||
)
|
||||
|
||||
type testEthkey struct {
|
||||
*cmdtest.TestCmd
|
||||
}
|
||||
|
||||
// spawns ethkey with the given command line args.
|
||||
func runEthkey(t *testing.T, args ...string) *testEthkey {
|
||||
tt := new(testEthkey)
|
||||
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
||||
tt.Run("ethkey-test", args...)
|
||||
return tt
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// Run the app if we've been exec'd as "ethkey-test" in runEthkey.
|
||||
reexec.Register("ethkey-test", func() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
})
|
||||
// check if we have been reexec'd
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
83
cmd/ethkey/utils.go
Normal file
83
cmd/ethkey/utils.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// getPassPhrase obtains a passphrase given by the user. It first checks the
|
||||
// --passphrase command line flag and ultimately prompts the user for a
|
||||
// passphrase.
|
||||
func getPassPhrase(ctx *cli.Context, confirmation bool) string {
|
||||
// Look for the --passphrase flag.
|
||||
passphraseFile := ctx.String(passphraseFlag.Name)
|
||||
if passphraseFile != "" {
|
||||
content, err := ioutil.ReadFile(passphraseFile)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase file '%s': %v",
|
||||
passphraseFile, err)
|
||||
}
|
||||
return strings.TrimRight(string(content), "\r\n")
|
||||
}
|
||||
|
||||
// Otherwise prompt the user for the passphrase.
|
||||
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||
}
|
||||
if confirmation {
|
||||
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
||||
}
|
||||
if passphrase != confirm {
|
||||
utils.Fatalf("Passphrases do not match")
|
||||
}
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
// signHash is a helper function that calculates a hash for the given message
|
||||
// that can be safely used to calculate a signature from.
|
||||
//
|
||||
// The hash is calulcated as
|
||||
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||
//
|
||||
// This gives context to the signed message and prevents signing of transactions.
|
||||
func signHash(data []byte) []byte {
|
||||
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||
return crypto.Keccak256([]byte(msg))
|
||||
}
|
||||
|
||||
// mustPrintJSON prints the JSON encoding of the given object and
|
||||
// exits the program with an error message when the marshaling fails.
|
||||
func mustPrintJSON(jsonObject interface{}) {
|
||||
str, err := json.MarshalIndent(jsonObject, "", " ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to marshal JSON object: %v", err)
|
||||
}
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
@@ -1,24 +1,25 @@
|
||||
// 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
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
// 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"
|
||||
"io"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -35,6 +36,10 @@ func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
|
||||
return &JSONLogger{json.NewEncoder(writer), cfg}
|
||||
}
|
||||
|
||||
func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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{
|
||||
@@ -56,6 +61,11 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
|
||||
return l.encoder.Encode(log)
|
||||
}
|
||||
|
||||
// CaptureFault outputs state information on the logger.
|
||||
func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CaptureEnd is triggered at end of execution.
|
||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||
type endLog struct {
|
||||
|
||||
@@ -96,7 +96,9 @@ func runCmd(ctx *cli.Context) error {
|
||||
}
|
||||
if ctx.GlobalString(GenesisFlag.Name) != "" {
|
||||
gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
|
||||
_, statedb = gen.ToBlock()
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
genesis := gen.ToBlock(db)
|
||||
statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
|
||||
chainConfig = gen.Config
|
||||
} else {
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
package main
|
||||
|
||||
//go:generate go-bindata -nometadata -o website.go faucet.html
|
||||
//go:generate gofmt -w -s website.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -83,7 +83,8 @@ var (
|
||||
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")
|
||||
noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication")
|
||||
logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet")
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -132,6 +133,7 @@ func main() {
|
||||
"Amounts": amounts,
|
||||
"Periods": periods,
|
||||
"Recaptcha": *captchaToken,
|
||||
"NoAuth": *noauthFlag,
|
||||
})
|
||||
if err != nil {
|
||||
log.Crit("Failed to render the faucet template", "err", err)
|
||||
@@ -221,7 +223,6 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
||||
NoDiscovery: true,
|
||||
DiscoveryV5: true,
|
||||
ListenAddr: fmt.Sprintf(":%d", port),
|
||||
DiscoveryV5Addr: fmt.Sprintf(":%d", port+1),
|
||||
MaxPeers: 25,
|
||||
BootstrapNodesV5: enodes,
|
||||
},
|
||||
@@ -374,7 +375,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||
if err = websocket.JSON.Receive(conn, &msg); err != nil {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
|
||||
if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
|
||||
!strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
|
||||
if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil {
|
||||
log.Warn("Failed to send URL error to client", "err", err)
|
||||
@@ -435,13 +436,19 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||
)
|
||||
switch {
|
||||
case strings.HasPrefix(msg.URL, "https://gist.github.com/"):
|
||||
username, avatar, address, err = authGitHub(msg.URL)
|
||||
if err = sendError(conn, errors.New("GitHub authentication discontinued at the official request of GitHub")); err != nil {
|
||||
log.Warn("Failed to send GitHub deprecation to client", "err", err)
|
||||
return
|
||||
}
|
||||
continue
|
||||
case strings.HasPrefix(msg.URL, "https://twitter.com/"):
|
||||
username, avatar, address, err = authTwitter(msg.URL)
|
||||
case strings.HasPrefix(msg.URL, "https://plus.google.com/"):
|
||||
username, avatar, address, err = authGooglePlus(msg.URL)
|
||||
case strings.HasPrefix(msg.URL, "https://www.facebook.com/"):
|
||||
username, avatar, address, err = authFacebook(msg.URL)
|
||||
case *noauthFlag:
|
||||
username, avatar, address, err = authNoAuth(msg.URL)
|
||||
default:
|
||||
err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
|
||||
}
|
||||
@@ -466,7 +473,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||
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)
|
||||
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil)
|
||||
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
||||
if err != nil {
|
||||
f.lock.Unlock()
|
||||
@@ -498,7 +505,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||
|
||||
// Send an error if too frequent funding, othewise a success
|
||||
if !fund {
|
||||
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil {
|
||||
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil { // nolint: gosimple
|
||||
log.Warn("Failed to send funding error to client", "err", err)
|
||||
return
|
||||
}
|
||||
@@ -690,11 +697,7 @@ func authTwitter(url string) (string, string, common.Address, error) {
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
reader, err := zlib.NewReader(res.Body)
|
||||
if err != nil {
|
||||
return "", "", common.Address{}, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(reader)
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", common.Address{}, err
|
||||
}
|
||||
@@ -776,3 +779,14 @@ func authFacebook(url string) (string, string, common.Address, error) {
|
||||
}
|
||||
return username + "@facebook", avatar, address, nil
|
||||
}
|
||||
|
||||
// authNoAuth tries to interpret a faucet request as a plain Ethereum address,
|
||||
// without actually performing any remote authentication. This mode is prone to
|
||||
// Byzantine attack, so only ever use for truly private networks.
|
||||
func authNoAuth(url string) (string, string, common.Address, error) {
|
||||
address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(url))
|
||||
if address == (common.Address{}) {
|
||||
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
|
||||
}
|
||||
return address.Hex() + "@noauth", "", address, nil
|
||||
}
|
||||
|
||||
@@ -80,11 +80,8 @@
|
||||
<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 certain common 3rd party accounts. Anyone having a GitHub, Twitter, Google+ or Facebook account may request funds within the permitted limits.</p>
|
||||
<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 common 3rd party social network accounts. Anyone having a Twitter, Google+ or Facebook account may request funds within the permitted limits.</p>
|
||||
<dl class="dl-horizontal">
|
||||
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-github-alt" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via GitHub, create a <a href="https://gist.github.com/" target="_about:blank">gist</a> with your Ethereum address embedded into the content (the file name doesn't matter).<br/>Copy-paste the gists URL into the above input box and fire away!</dd>
|
||||
|
||||
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-twitter" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Twitter, make a <a href="https://twitter.com/intent/tweet?text=Requesting%20faucet%20funds%20into%200x0000000000000000000000000000000000000000%20on%20the%20%23{{.Network}}%20%23Ethereum%20test%20network." target="_about:blank">tweet</a> with your Ethereum address pasted into the contents (surrounding text doesn't matter).<br/>Copy-paste the <a href="https://support.twitter.com/articles/80586" target="_about:blank">tweets URL</a> into the above input box and fire away!</dd>
|
||||
|
||||
@@ -93,6 +90,11 @@
|
||||
|
||||
<dt style="width: auto; margin-left: 40px;"><i class="fa fa-facebook" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||
<dd style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds via Facebook, publish a new <strong>public</strong> post with your Ethereum address embedded into the content (surrounding text doesn't matter).<br/>Copy-paste the <a href="https://www.facebook.com/help/community/question/?id=282662498552845" target="_about:blank">posts URL</a> into the above input box and fire away!</dd>
|
||||
|
||||
{{if .NoAuth}}
|
||||
<dt class="text-danger" style="width: auto; margin-left: 40px;"><i class="fa fa-unlock-alt" aria-hidden="true" style="font-size: 36px;"></i></dt>
|
||||
<dd class="text-danger" style="margin-left: 88px; margin-bottom: 10px;"></i> To request funds <strong>without authentication</strong>, simply copy-paste your Ethereum address into the above input box (surrounding text doesn't matter) and fire away.<br/>This mode is susceptible to Byzantine attacks. Only use for debugging or private networks!</dd>
|
||||
{{end}}
|
||||
</dl>
|
||||
<p>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}}
|
||||
@@ -126,12 +128,7 @@
|
||||
};
|
||||
// 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 = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
|
||||
|
||||
server.onmessage = function(event) {
|
||||
var msg = JSON.parse(event.data);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -202,7 +202,7 @@ func importChain(ctx *cli.Context) error {
|
||||
|
||||
if len(ctx.Args()) == 1 {
|
||||
if err := utils.ImportChain(chain, ctx.Args().First()); err != nil {
|
||||
utils.Fatalf("Import error: %v", err)
|
||||
log.Error("Import error", "err", err)
|
||||
}
|
||||
} else {
|
||||
for _, arg := range ctx.Args() {
|
||||
@@ -211,7 +211,7 @@ func importChain(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.Stop()
|
||||
fmt.Printf("Import done in %v.\n\n", time.Since(start))
|
||||
|
||||
// Output pre-compaction stats mostly to see the import trashing
|
||||
|
||||
@@ -18,7 +18,6 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -29,7 +28,6 @@ import (
|
||||
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/dashboard"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
@@ -158,7 +156,7 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
utils.RegisterEthService(stack, &cfg.Eth)
|
||||
|
||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||
utils.RegisterDashboardService(stack, &cfg.Dashboard)
|
||||
utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
|
||||
}
|
||||
// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
|
||||
shhEnabled := enableWhisper(ctx)
|
||||
@@ -177,21 +175,6 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
@@ -112,7 +114,22 @@ func localConsole(ctx *cli.Context) error {
|
||||
// console to it.
|
||||
func remoteConsole(ctx *cli.Context) error {
|
||||
// Attach to a remotely running geth instance and start the JavaScript console
|
||||
client, err := dialRPC(ctx.Args().First())
|
||||
endpoint := ctx.Args().First()
|
||||
if endpoint == "" {
|
||||
path := node.DefaultDataDir()
|
||||
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
||||
path = ctx.GlobalString(utils.DataDirFlag.Name)
|
||||
}
|
||||
if path != "" {
|
||||
if ctx.GlobalBool(utils.TestnetFlag.Name) {
|
||||
path = filepath.Join(path, "testnet")
|
||||
} else if ctx.GlobalBool(utils.RinkebyFlag.Name) {
|
||||
path = filepath.Join(path, "rinkeby")
|
||||
}
|
||||
}
|
||||
endpoint = fmt.Sprintf("%s/geth.ipc", path)
|
||||
}
|
||||
client, err := dialRPC(endpoint)
|
||||
if err != nil {
|
||||
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
||||
}
|
||||
|
||||
@@ -85,10 +85,13 @@ var (
|
||||
utils.FastSyncFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.GCModeFlag,
|
||||
utils.LightServFlag,
|
||||
utils.LightPeersFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.TrieCacheGenFlag,
|
||||
utils.ListenPortFlag,
|
||||
utils.MaxPeersFlag,
|
||||
@@ -111,6 +114,7 @@ var (
|
||||
utils.VMEnableDebugFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.RPCCORSDomainFlag,
|
||||
utils.RPCVirtualHostsFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.FakePoWFlag,
|
||||
@@ -278,9 +282,12 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
// Start auxiliary services if enabled
|
||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
||||
// Mining only makes sense if a full Ethereum node is running
|
||||
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||
utils.Fatalf("Light clients do not support mining")
|
||||
}
|
||||
var ethereum *eth.Ethereum
|
||||
if err := stack.Service(ðereum); err != nil {
|
||||
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 {
|
||||
|
||||
@@ -134,7 +134,6 @@ 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 geth. If not, see <http://www.gnu.org/licenses/>.
|
||||
`)
|
||||
along with geth. If not, see <http://www.gnu.org/licenses/>.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ import (
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AppHelpTemplate is the test template for the default, global app help topic.
|
||||
@@ -74,6 +75,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.TestnetFlag,
|
||||
utils.RinkebyFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.GCModeFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.IdentityFlag,
|
||||
utils.LightServFlag,
|
||||
@@ -127,6 +129,8 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
Name: "PERFORMANCE TUNING",
|
||||
Flags: []cli.Flag{
|
||||
utils.CacheFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheGCFlag,
|
||||
utils.TrieCacheGenFlag,
|
||||
},
|
||||
},
|
||||
@@ -152,6 +156,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.RPCCORSDomainFlag,
|
||||
utils.RPCVirtualHostsFlag,
|
||||
utils.JSpathFlag,
|
||||
utils.ExecFlag,
|
||||
utils.PreloadJSFlag,
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// 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/>.
|
||||
|
||||
// p2psim provides a command-line client for a simulation HTTP API.
|
||||
//
|
||||
// Here is an example of creating a 2 node network with the first node
|
||||
|
||||
379
cmd/puppeth/genesis.go
Normal file
379
cmd/puppeth/genesis.go
Normal file
@@ -0,0 +1,379 @@
|
||||
// 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/binary"
|
||||
"errors"
|
||||
"math"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// cppEthereumGenesisSpec represents the genesis specification format used by the
|
||||
// C++ Ethereum implementation.
|
||||
type cppEthereumGenesisSpec struct {
|
||||
SealEngine string `json:"sealEngine"`
|
||||
Params struct {
|
||||
AccountStartNonce hexutil.Uint64 `json:"accountStartNonce"`
|
||||
HomesteadForkBlock hexutil.Uint64 `json:"homesteadForkBlock"`
|
||||
EIP150ForkBlock hexutil.Uint64 `json:"EIP150ForkBlock"`
|
||||
EIP158ForkBlock hexutil.Uint64 `json:"EIP158ForkBlock"`
|
||||
ByzantiumForkBlock hexutil.Uint64 `json:"byzantiumForkBlock"`
|
||||
ConstantinopleForkBlock hexutil.Uint64 `json:"constantinopleForkBlock"`
|
||||
NetworkID hexutil.Uint64 `json:"networkID"`
|
||||
ChainID hexutil.Uint64 `json:"chainID"`
|
||||
MaximumExtraDataSize hexutil.Uint64 `json:"maximumExtraDataSize"`
|
||||
MinGasLimit hexutil.Uint64 `json:"minGasLimit"`
|
||||
MaxGasLimit hexutil.Uint64 `json:"maxGasLimit"`
|
||||
GasLimitBoundDivisor hexutil.Uint64 `json:"gasLimitBoundDivisor"`
|
||||
MinimumDifficulty *hexutil.Big `json:"minimumDifficulty"`
|
||||
DifficultyBoundDivisor *hexutil.Big `json:"difficultyBoundDivisor"`
|
||||
DurationLimit *hexutil.Big `json:"durationLimit"`
|
||||
BlockReward *hexutil.Big `json:"blockReward"`
|
||||
} `json:"params"`
|
||||
|
||||
Genesis struct {
|
||||
Nonce hexutil.Bytes `json:"nonce"`
|
||||
Difficulty *hexutil.Big `json:"difficulty"`
|
||||
MixHash common.Hash `json:"mixHash"`
|
||||
Author common.Address `json:"author"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit"`
|
||||
} `json:"genesis"`
|
||||
|
||||
Accounts map[common.Address]*cppEthereumGenesisSpecAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// cppEthereumGenesisSpecAccount is the prefunded genesis account and/or precompiled
|
||||
// contract definition.
|
||||
type cppEthereumGenesisSpecAccount struct {
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
Nonce uint64 `json:"nonce,omitempty"`
|
||||
Precompiled *cppEthereumGenesisSpecBuiltin `json:"precompiled,omitempty"`
|
||||
}
|
||||
|
||||
// cppEthereumGenesisSpecBuiltin is the precompiled contract definition.
|
||||
type cppEthereumGenesisSpecBuiltin struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
StartingBlock hexutil.Uint64 `json:"startingBlock,omitempty"`
|
||||
Linear *cppEthereumGenesisSpecLinearPricing `json:"linear,omitempty"`
|
||||
}
|
||||
|
||||
type cppEthereumGenesisSpecLinearPricing struct {
|
||||
Base uint64 `json:"base"`
|
||||
Word uint64 `json:"word"`
|
||||
}
|
||||
|
||||
// newCppEthereumGenesisSpec converts a go-ethereum genesis block into a Parity specific
|
||||
// chain specification format.
|
||||
func newCppEthereumGenesisSpec(network string, genesis *core.Genesis) (*cppEthereumGenesisSpec, error) {
|
||||
// Only ethash is currently supported between go-ethereum and cpp-ethereum
|
||||
if genesis.Config.Ethash == nil {
|
||||
return nil, errors.New("unsupported consensus engine")
|
||||
}
|
||||
// Reconstruct the chain spec in Parity's format
|
||||
spec := &cppEthereumGenesisSpec{
|
||||
SealEngine: "Ethash",
|
||||
}
|
||||
spec.Params.AccountStartNonce = 0
|
||||
spec.Params.HomesteadForkBlock = (hexutil.Uint64)(genesis.Config.HomesteadBlock.Uint64())
|
||||
spec.Params.EIP150ForkBlock = (hexutil.Uint64)(genesis.Config.EIP150Block.Uint64())
|
||||
spec.Params.EIP158ForkBlock = (hexutil.Uint64)(genesis.Config.EIP158Block.Uint64())
|
||||
spec.Params.ByzantiumForkBlock = (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64())
|
||||
spec.Params.ConstantinopleForkBlock = (hexutil.Uint64)(math.MaxUint64)
|
||||
|
||||
spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainId.Uint64())
|
||||
spec.Params.ChainID = (hexutil.Uint64)(genesis.Config.ChainId.Uint64())
|
||||
|
||||
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
|
||||
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
|
||||
spec.Params.MaxGasLimit = (hexutil.Uint64)(math.MaxUint64)
|
||||
spec.Params.MinimumDifficulty = (*hexutil.Big)(params.MinimumDifficulty)
|
||||
spec.Params.DifficultyBoundDivisor = (*hexutil.Big)(params.DifficultyBoundDivisor)
|
||||
spec.Params.GasLimitBoundDivisor = (hexutil.Uint64)(params.GasLimitBoundDivisor)
|
||||
spec.Params.DurationLimit = (*hexutil.Big)(params.DurationLimit)
|
||||
spec.Params.BlockReward = (*hexutil.Big)(ethash.FrontierBlockReward)
|
||||
|
||||
spec.Genesis.Nonce = (hexutil.Bytes)(make([]byte, 8))
|
||||
binary.LittleEndian.PutUint64(spec.Genesis.Nonce[:], genesis.Nonce)
|
||||
|
||||
spec.Genesis.MixHash = genesis.Mixhash
|
||||
spec.Genesis.Difficulty = (*hexutil.Big)(genesis.Difficulty)
|
||||
spec.Genesis.Author = genesis.Coinbase
|
||||
spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp)
|
||||
spec.Genesis.ParentHash = genesis.ParentHash
|
||||
spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData)
|
||||
spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit)
|
||||
|
||||
spec.Accounts = make(map[common.Address]*cppEthereumGenesisSpecAccount)
|
||||
for address, account := range genesis.Alloc {
|
||||
spec.Accounts[address] = &cppEthereumGenesisSpecAccount{
|
||||
Balance: (*hexutil.Big)(account.Balance),
|
||||
Nonce: account.Nonce,
|
||||
}
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{1})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "ecrecover", Linear: &cppEthereumGenesisSpecLinearPricing{Base: 3000},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{2})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "sha256", Linear: &cppEthereumGenesisSpecLinearPricing{Base: 60, Word: 12},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{3})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "ripemd160", Linear: &cppEthereumGenesisSpecLinearPricing{Base: 600, Word: 120},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{4})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "identity", Linear: &cppEthereumGenesisSpecLinearPricing{Base: 15, Word: 3},
|
||||
}
|
||||
if genesis.Config.ByzantiumBlock != nil {
|
||||
spec.Accounts[common.BytesToAddress([]byte{5})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "modexp", StartingBlock: (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64()),
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{6})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "alt_bn128_G1_add", StartingBlock: (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64()), Linear: &cppEthereumGenesisSpecLinearPricing{Base: 500},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{7})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "alt_bn128_G1_mul", StartingBlock: (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64()), Linear: &cppEthereumGenesisSpecLinearPricing{Base: 40000},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{8})].Precompiled = &cppEthereumGenesisSpecBuiltin{
|
||||
Name: "alt_bn128_pairing_product", StartingBlock: (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64()),
|
||||
}
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// parityChainSpec is the chain specification format used by Parity.
|
||||
type parityChainSpec struct {
|
||||
Name string `json:"name"`
|
||||
Engine struct {
|
||||
Ethash struct {
|
||||
Params struct {
|
||||
MinimumDifficulty *hexutil.Big `json:"minimumDifficulty"`
|
||||
DifficultyBoundDivisor *hexutil.Big `json:"difficultyBoundDivisor"`
|
||||
GasLimitBoundDivisor hexutil.Uint64 `json:"gasLimitBoundDivisor"`
|
||||
DurationLimit *hexutil.Big `json:"durationLimit"`
|
||||
BlockReward *hexutil.Big `json:"blockReward"`
|
||||
HomesteadTransition uint64 `json:"homesteadTransition"`
|
||||
EIP150Transition uint64 `json:"eip150Transition"`
|
||||
EIP160Transition uint64 `json:"eip160Transition"`
|
||||
EIP161abcTransition uint64 `json:"eip161abcTransition"`
|
||||
EIP161dTransition uint64 `json:"eip161dTransition"`
|
||||
EIP649Reward *hexutil.Big `json:"eip649Reward"`
|
||||
EIP100bTransition uint64 `json:"eip100bTransition"`
|
||||
EIP649Transition uint64 `json:"eip649Transition"`
|
||||
} `json:"params"`
|
||||
} `json:"Ethash"`
|
||||
} `json:"engine"`
|
||||
|
||||
Params struct {
|
||||
MaximumExtraDataSize hexutil.Uint64 `json:"maximumExtraDataSize"`
|
||||
MinGasLimit hexutil.Uint64 `json:"minGasLimit"`
|
||||
NetworkID hexutil.Uint64 `json:"networkID"`
|
||||
MaxCodeSize uint64 `json:"maxCodeSize"`
|
||||
EIP155Transition uint64 `json:"eip155Transition"`
|
||||
EIP98Transition uint64 `json:"eip98Transition"`
|
||||
EIP86Transition uint64 `json:"eip86Transition"`
|
||||
EIP140Transition uint64 `json:"eip140Transition"`
|
||||
EIP211Transition uint64 `json:"eip211Transition"`
|
||||
EIP214Transition uint64 `json:"eip214Transition"`
|
||||
EIP658Transition uint64 `json:"eip658Transition"`
|
||||
} `json:"params"`
|
||||
|
||||
Genesis struct {
|
||||
Seal struct {
|
||||
Ethereum struct {
|
||||
Nonce hexutil.Bytes `json:"nonce"`
|
||||
MixHash hexutil.Bytes `json:"mixHash"`
|
||||
} `json:"ethereum"`
|
||||
} `json:"seal"`
|
||||
|
||||
Difficulty *hexutil.Big `json:"difficulty"`
|
||||
Author common.Address `json:"author"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit"`
|
||||
} `json:"genesis"`
|
||||
|
||||
Nodes []string `json:"nodes"`
|
||||
Accounts map[common.Address]*parityChainSpecAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// parityChainSpecAccount is the prefunded genesis account and/or precompiled
|
||||
// contract definition.
|
||||
type parityChainSpecAccount struct {
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
Nonce uint64 `json:"nonce,omitempty"`
|
||||
Builtin *parityChainSpecBuiltin `json:"builtin,omitempty"`
|
||||
}
|
||||
|
||||
// parityChainSpecBuiltin is the precompiled contract definition.
|
||||
type parityChainSpecBuiltin struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ActivateAt uint64 `json:"activate_at,omitempty"`
|
||||
Pricing *parityChainSpecPricing `json:"pricing,omitempty"`
|
||||
}
|
||||
|
||||
// parityChainSpecPricing represents the different pricing models that builtin
|
||||
// contracts might advertise using.
|
||||
type parityChainSpecPricing struct {
|
||||
Linear *parityChainSpecLinearPricing `json:"linear,omitempty"`
|
||||
ModExp *parityChainSpecModExpPricing `json:"modexp,omitempty"`
|
||||
AltBnPairing *parityChainSpecAltBnPairingPricing `json:"alt_bn128_pairing,omitempty"`
|
||||
}
|
||||
|
||||
type parityChainSpecLinearPricing struct {
|
||||
Base uint64 `json:"base"`
|
||||
Word uint64 `json:"word"`
|
||||
}
|
||||
|
||||
type parityChainSpecModExpPricing struct {
|
||||
Divisor uint64 `json:"divisor"`
|
||||
}
|
||||
|
||||
type parityChainSpecAltBnPairingPricing struct {
|
||||
Base uint64 `json:"base"`
|
||||
Pair uint64 `json:"pair"`
|
||||
}
|
||||
|
||||
// newParityChainSpec converts a go-ethereum genesis block into a Parity specific
|
||||
// chain specification format.
|
||||
func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []string) (*parityChainSpec, error) {
|
||||
// Only ethash is currently supported between go-ethereum and Parity
|
||||
if genesis.Config.Ethash == nil {
|
||||
return nil, errors.New("unsupported consensus engine")
|
||||
}
|
||||
// Reconstruct the chain spec in Parity's format
|
||||
spec := &parityChainSpec{
|
||||
Name: network,
|
||||
Nodes: bootnodes,
|
||||
}
|
||||
spec.Engine.Ethash.Params.MinimumDifficulty = (*hexutil.Big)(params.MinimumDifficulty)
|
||||
spec.Engine.Ethash.Params.DifficultyBoundDivisor = (*hexutil.Big)(params.DifficultyBoundDivisor)
|
||||
spec.Engine.Ethash.Params.GasLimitBoundDivisor = (hexutil.Uint64)(params.GasLimitBoundDivisor)
|
||||
spec.Engine.Ethash.Params.DurationLimit = (*hexutil.Big)(params.DurationLimit)
|
||||
spec.Engine.Ethash.Params.BlockReward = (*hexutil.Big)(ethash.FrontierBlockReward)
|
||||
spec.Engine.Ethash.Params.HomesteadTransition = genesis.Config.HomesteadBlock.Uint64()
|
||||
spec.Engine.Ethash.Params.EIP150Transition = genesis.Config.EIP150Block.Uint64()
|
||||
spec.Engine.Ethash.Params.EIP160Transition = genesis.Config.EIP155Block.Uint64()
|
||||
spec.Engine.Ethash.Params.EIP161abcTransition = genesis.Config.EIP158Block.Uint64()
|
||||
spec.Engine.Ethash.Params.EIP161dTransition = genesis.Config.EIP158Block.Uint64()
|
||||
spec.Engine.Ethash.Params.EIP649Reward = (*hexutil.Big)(ethash.ByzantiumBlockReward)
|
||||
spec.Engine.Ethash.Params.EIP100bTransition = genesis.Config.ByzantiumBlock.Uint64()
|
||||
spec.Engine.Ethash.Params.EIP649Transition = genesis.Config.ByzantiumBlock.Uint64()
|
||||
|
||||
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
|
||||
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
|
||||
spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainId.Uint64())
|
||||
spec.Params.MaxCodeSize = params.MaxCodeSize
|
||||
spec.Params.EIP155Transition = genesis.Config.EIP155Block.Uint64()
|
||||
spec.Params.EIP98Transition = math.MaxUint64
|
||||
spec.Params.EIP86Transition = math.MaxUint64
|
||||
spec.Params.EIP140Transition = genesis.Config.ByzantiumBlock.Uint64()
|
||||
spec.Params.EIP211Transition = genesis.Config.ByzantiumBlock.Uint64()
|
||||
spec.Params.EIP214Transition = genesis.Config.ByzantiumBlock.Uint64()
|
||||
spec.Params.EIP658Transition = genesis.Config.ByzantiumBlock.Uint64()
|
||||
|
||||
spec.Genesis.Seal.Ethereum.Nonce = (hexutil.Bytes)(make([]byte, 8))
|
||||
binary.LittleEndian.PutUint64(spec.Genesis.Seal.Ethereum.Nonce[:], genesis.Nonce)
|
||||
|
||||
spec.Genesis.Seal.Ethereum.MixHash = (hexutil.Bytes)(genesis.Mixhash[:])
|
||||
spec.Genesis.Difficulty = (*hexutil.Big)(genesis.Difficulty)
|
||||
spec.Genesis.Author = genesis.Coinbase
|
||||
spec.Genesis.Timestamp = (hexutil.Uint64)(genesis.Timestamp)
|
||||
spec.Genesis.ParentHash = genesis.ParentHash
|
||||
spec.Genesis.ExtraData = (hexutil.Bytes)(genesis.ExtraData)
|
||||
spec.Genesis.GasLimit = (hexutil.Uint64)(genesis.GasLimit)
|
||||
|
||||
spec.Accounts = make(map[common.Address]*parityChainSpecAccount)
|
||||
for address, account := range genesis.Alloc {
|
||||
spec.Accounts[address] = &parityChainSpecAccount{
|
||||
Balance: (*hexutil.Big)(account.Balance),
|
||||
Nonce: account.Nonce,
|
||||
}
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{1})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "ecrecover", Pricing: &parityChainSpecPricing{Linear: &parityChainSpecLinearPricing{Base: 3000}},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{2})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "sha256", Pricing: &parityChainSpecPricing{Linear: &parityChainSpecLinearPricing{Base: 60, Word: 12}},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{3})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "ripemd160", Pricing: &parityChainSpecPricing{Linear: &parityChainSpecLinearPricing{Base: 600, Word: 120}},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{4})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "identity", Pricing: &parityChainSpecPricing{Linear: &parityChainSpecLinearPricing{Base: 15, Word: 3}},
|
||||
}
|
||||
if genesis.Config.ByzantiumBlock != nil {
|
||||
spec.Accounts[common.BytesToAddress([]byte{5})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "modexp", ActivateAt: genesis.Config.ByzantiumBlock.Uint64(), Pricing: &parityChainSpecPricing{ModExp: &parityChainSpecModExpPricing{Divisor: 20}},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{6})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "alt_bn128_add", ActivateAt: genesis.Config.ByzantiumBlock.Uint64(), Pricing: &parityChainSpecPricing{Linear: &parityChainSpecLinearPricing{Base: 500}},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{7})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "alt_bn128_mul", ActivateAt: genesis.Config.ByzantiumBlock.Uint64(), Pricing: &parityChainSpecPricing{Linear: &parityChainSpecLinearPricing{Base: 40000}},
|
||||
}
|
||||
spec.Accounts[common.BytesToAddress([]byte{8})].Builtin = &parityChainSpecBuiltin{
|
||||
Name: "alt_bn128_pairing", ActivateAt: genesis.Config.ByzantiumBlock.Uint64(), Pricing: &parityChainSpecPricing{AltBnPairing: &parityChainSpecAltBnPairingPricing{Base: 100000, Pair: 80000}},
|
||||
}
|
||||
}
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// pyEthereumGenesisSpec represents the genesis specification format used by the
|
||||
// Python Ethereum implementation.
|
||||
type pyEthereumGenesisSpec struct {
|
||||
Nonce hexutil.Bytes `json:"nonce"`
|
||||
Timestamp hexutil.Uint64 `json:"timestamp"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit"`
|
||||
Difficulty *hexutil.Big `json:"difficulty"`
|
||||
Mixhash common.Hash `json:"mixhash"`
|
||||
Coinbase common.Address `json:"coinbase"`
|
||||
Alloc core.GenesisAlloc `json:"alloc"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
}
|
||||
|
||||
// newPyEthereumGenesisSpec converts a go-ethereum genesis block into a Parity specific
|
||||
// chain specification format.
|
||||
func newPyEthereumGenesisSpec(network string, genesis *core.Genesis) (*pyEthereumGenesisSpec, error) {
|
||||
// Only ethash is currently supported between go-ethereum and pyethereum
|
||||
if genesis.Config.Ethash == nil {
|
||||
return nil, errors.New("unsupported consensus engine")
|
||||
}
|
||||
spec := &pyEthereumGenesisSpec{
|
||||
Timestamp: (hexutil.Uint64)(genesis.Timestamp),
|
||||
ExtraData: genesis.ExtraData,
|
||||
GasLimit: (hexutil.Uint64)(genesis.GasLimit),
|
||||
Difficulty: (*hexutil.Big)(genesis.Difficulty),
|
||||
Mixhash: genesis.Mixhash,
|
||||
Coinbase: genesis.Coinbase,
|
||||
Alloc: genesis.Alloc,
|
||||
ParentHash: genesis.ParentHash,
|
||||
}
|
||||
spec.Nonce = (hexutil.Bytes)(make([]byte, 8))
|
||||
binary.LittleEndian.PutUint64(spec.Nonce[:], genesis.Nonce)
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -30,21 +31,9 @@ import (
|
||||
// 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
|
||||
FROM puppeth/ethstats:latest
|
||||
|
||||
RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: [{{.Banned}}], reserved: ["yournode"]};' > lib/utils/config.js
|
||||
|
||||
CMD ["npm", "start"]
|
||||
`
|
||||
|
||||
// ethstatsComposefile is the docker-compose.yml file required to deploy and
|
||||
@@ -72,7 +61,7 @@ services:
|
||||
// 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, banned []string) ([]byte, error) {
|
||||
func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string, banned []string, nocache bool) ([]byte, error) {
|
||||
// Generate the content to upload to the server
|
||||
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||
files := make(map[string][]byte)
|
||||
@@ -110,7 +99,10 @@ func deployEthstats(client *sshClient, network string, port int, secret string,
|
||||
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))
|
||||
if nocache {
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
||||
}
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||
}
|
||||
|
||||
// ethstatsInfos is returned from an ethstats status check to allow reporting
|
||||
@@ -123,9 +115,15 @@ type ethstatsInfos struct {
|
||||
banned []string
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (info *ethstatsInfos) String() string {
|
||||
return fmt.Sprintf("host=%s, port=%d, secret=%s, banned=%v", info.host, info.port, info.secret, info.banned)
|
||||
// Report converts the typed struct into a plain string->string map, containing
|
||||
// most - but not all - fields for reporting to the user.
|
||||
func (info *ethstatsInfos) Report() map[string]string {
|
||||
return map[string]string{
|
||||
"Website address": info.host,
|
||||
"Website listener port": strconv.Itoa(info.port),
|
||||
"Login secret": info.secret,
|
||||
"Banned addresses": fmt.Sprintf("%v", info.banned),
|
||||
}
|
||||
}
|
||||
|
||||
// checkEthstats does a health-check against an ethstats server to verify whether
|
||||
|
||||
211
cmd/puppeth/module_explorer.go
Normal file
211
cmd/puppeth/module_explorer.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// explorerDockerfile is the Dockerfile required to run a block explorer.
|
||||
var explorerDockerfile = `
|
||||
FROM puppeth/explorer:latest
|
||||
|
||||
ADD ethstats.json /ethstats.json
|
||||
ADD chain.json /chain.json
|
||||
|
||||
RUN \
|
||||
echo '(cd ../eth-net-intelligence-api && pm2 start /ethstats.json)' > explorer.sh && \
|
||||
echo '(cd ../etherchain-light && npm start &)' >> explorer.sh && \
|
||||
echo '/parity/parity --chain=/chain.json --port={{.NodePort}} --tracing=on --fat-db=on --pruning=archive' >> explorer.sh
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "explorer.sh"]
|
||||
`
|
||||
|
||||
// explorerEthstats is the configuration file for the ethstats javascript client.
|
||||
var explorerEthstats = `[
|
||||
{
|
||||
"name" : "node-app",
|
||||
"script" : "app.js",
|
||||
"log_date_format" : "YYYY-MM-DD HH:mm Z",
|
||||
"merge_logs" : false,
|
||||
"watch" : false,
|
||||
"max_restarts" : 10,
|
||||
"exec_interpreter" : "node",
|
||||
"exec_mode" : "fork_mode",
|
||||
"env":
|
||||
{
|
||||
"NODE_ENV" : "production",
|
||||
"RPC_HOST" : "localhost",
|
||||
"RPC_PORT" : "8545",
|
||||
"LISTENING_PORT" : "{{.Port}}",
|
||||
"INSTANCE_NAME" : "{{.Name}}",
|
||||
"CONTACT_DETAILS" : "",
|
||||
"WS_SERVER" : "{{.Host}}",
|
||||
"WS_SECRET" : "{{.Secret}}",
|
||||
"VERBOSITY" : 2
|
||||
}
|
||||
}
|
||||
]`
|
||||
|
||||
// explorerComposefile is the docker-compose.yml file required to deploy and
|
||||
// maintain a block explorer.
|
||||
var explorerComposefile = `
|
||||
version: '2'
|
||||
services:
|
||||
explorer:
|
||||
build: .
|
||||
image: {{.Network}}/explorer
|
||||
ports:
|
||||
- "{{.NodePort}}:{{.NodePort}}"
|
||||
- "{{.NodePort}}:{{.NodePort}}/udp"{{if not .VHost}}
|
||||
- "{{.WebPort}}:3000"{{end}}
|
||||
volumes:
|
||||
- {{.Datadir}}:/root/.local/share/io.parity.ethereum
|
||||
environment:
|
||||
- NODE_PORT={{.NodePort}}/tcp
|
||||
- STATS={{.Ethstats}}{{if .VHost}}
|
||||
- VIRTUAL_HOST={{.VHost}}
|
||||
- VIRTUAL_PORT=3000{{end}}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "1m"
|
||||
max-file: "10"
|
||||
restart: always
|
||||
`
|
||||
|
||||
// deployExplorer deploys a new block explorer 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 deployExplorer(client *sshClient, network string, chainspec []byte, config *explorerInfos, nocache bool) ([]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(explorerDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||
"NodePort": config.nodePort,
|
||||
})
|
||||
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||
|
||||
ethstats := new(bytes.Buffer)
|
||||
template.Must(template.New("").Parse(explorerEthstats)).Execute(ethstats, map[string]interface{}{
|
||||
"Port": config.nodePort,
|
||||
"Name": config.ethstats[:strings.Index(config.ethstats, ":")],
|
||||
"Secret": config.ethstats[strings.Index(config.ethstats, ":")+1 : strings.Index(config.ethstats, "@")],
|
||||
"Host": config.ethstats[strings.Index(config.ethstats, "@")+1:],
|
||||
})
|
||||
files[filepath.Join(workdir, "ethstats.json")] = ethstats.Bytes()
|
||||
|
||||
composefile := new(bytes.Buffer)
|
||||
template.Must(template.New("").Parse(explorerComposefile)).Execute(composefile, map[string]interface{}{
|
||||
"Datadir": config.datadir,
|
||||
"Network": network,
|
||||
"NodePort": config.nodePort,
|
||||
"VHost": config.webHost,
|
||||
"WebPort": config.webPort,
|
||||
"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
|
||||
})
|
||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||
|
||||
files[filepath.Join(workdir, "chain.json")] = chainspec
|
||||
|
||||
// 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
|
||||
if nocache {
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
||||
}
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||
}
|
||||
|
||||
// explorerInfos is returned from a block explorer status check to allow reporting
|
||||
// various configuration parameters.
|
||||
type explorerInfos struct {
|
||||
datadir string
|
||||
ethstats string
|
||||
nodePort int
|
||||
webHost string
|
||||
webPort int
|
||||
}
|
||||
|
||||
// Report converts the typed struct into a plain string->string map, containing
|
||||
// most - but not all - fields for reporting to the user.
|
||||
func (info *explorerInfos) Report() map[string]string {
|
||||
report := map[string]string{
|
||||
"Data directory": info.datadir,
|
||||
"Node listener port ": strconv.Itoa(info.nodePort),
|
||||
"Ethstats username": info.ethstats,
|
||||
"Website address ": info.webHost,
|
||||
"Website listener port ": strconv.Itoa(info.webPort),
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
// checkExplorer does a health-check against an block explorer server to verify
|
||||
// whether it's running, and if yes, whether it's responsive.
|
||||
func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
|
||||
// Inspect a possible block explorer container on the host
|
||||
infos, err := inspectContainer(client, fmt.Sprintf("%s_explorer_1", network))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !infos.running {
|
||||
return nil, ErrServiceOffline
|
||||
}
|
||||
// Resolve the port from the host, or the reverse proxy
|
||||
webPort := infos.portmap["3000/tcp"]
|
||||
if webPort == 0 {
|
||||
if proxy, _ := checkNginx(client, network); proxy != nil {
|
||||
webPort = proxy.port
|
||||
}
|
||||
}
|
||||
if webPort == 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
|
||||
}
|
||||
// Run a sanity check to see if the devp2p is reachable
|
||||
nodePort := infos.portmap[infos.envvars["NODE_PORT"]]
|
||||
if err = checkPort(client.server, nodePort); err != nil {
|
||||
log.Warn(fmt.Sprintf("Explorer devp2p port seems unreachable"), "server", client.server, "port", nodePort, "err", err)
|
||||
}
|
||||
// Assemble and return the useful infos
|
||||
stats := &explorerInfos{
|
||||
datadir: infos.volumes["/root/.local/share/io.parity.ethereum"],
|
||||
nodePort: nodePort,
|
||||
webHost: host,
|
||||
webPort: webPort,
|
||||
ethstats: infos.envvars["STATS"],
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
@@ -25,36 +26,26 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/*
|
||||
FROM ethereum/client-go:alltools-latest
|
||||
|
||||
ADD genesis.json /genesis.json
|
||||
ADD account.json /account.json
|
||||
ADD account.pass /account.pass
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8080 30303 30303/udp
|
||||
|
||||
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}} \
|
||||
ENTRYPOINT [ \
|
||||
"faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
|
||||
"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
|
||||
"--account.json", "/account.json", "--account.pass", "/account.pass" \
|
||||
{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \
|
||||
]`
|
||||
|
||||
// faucetComposefile is the docker-compose.yml file required to deploy and maintain
|
||||
@@ -76,10 +67,9 @@ services:
|
||||
- FAUCET_AMOUNT={{.FaucetAmount}}
|
||||
- FAUCET_MINUTES={{.FaucetMinutes}}
|
||||
- FAUCET_TIERS={{.FaucetTiers}}
|
||||
- GITHUB_USER={{.GitHubUser}}
|
||||
- GITHUB_TOKEN={{.GitHubToken}}
|
||||
- CAPTCHA_TOKEN={{.CaptchaToken}}
|
||||
- CAPTCHA_SECRET={{.CaptchaSecret}}{{if .VHost}}
|
||||
- CAPTCHA_SECRET={{.CaptchaSecret}}
|
||||
- NO_AUTH={{.NoAuth}}{{if .VHost}}
|
||||
- VIRTUAL_HOST={{.VHost}}
|
||||
- VIRTUAL_PORT=8080{{end}}
|
||||
logging:
|
||||
@@ -93,7 +83,7 @@ services:
|
||||
// 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) {
|
||||
func deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos, nocache bool) ([]byte, error) {
|
||||
// Generate the content to upload to the server
|
||||
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||
files := make(map[string][]byte)
|
||||
@@ -103,15 +93,14 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
||||
"NetworkID": config.node.network,
|
||||
"Bootnodes": strings.Join(bootnodes, ","),
|
||||
"Ethstats": config.node.ethstats,
|
||||
"EthPort": config.node.portFull,
|
||||
"GitHubUser": config.githubUser,
|
||||
"GitHubToken": config.githubToken,
|
||||
"EthPort": config.node.port,
|
||||
"CaptchaToken": config.captchaToken,
|
||||
"CaptchaSecret": config.captchaSecret,
|
||||
"FaucetName": strings.Title(network),
|
||||
"FaucetAmount": config.amount,
|
||||
"FaucetMinutes": config.minutes,
|
||||
"FaucetTiers": config.tiers,
|
||||
"NoAuth": config.noauth,
|
||||
})
|
||||
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||
|
||||
@@ -121,15 +110,14 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
||||
"Datadir": config.node.datadir,
|
||||
"VHost": config.host,
|
||||
"ApiPort": config.port,
|
||||
"EthPort": config.node.portFull,
|
||||
"EthPort": config.node.port,
|
||||
"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,
|
||||
"NoAuth": config.noauth,
|
||||
})
|
||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||
|
||||
@@ -144,7 +132,10 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
||||
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))
|
||||
if nocache {
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
||||
}
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||
}
|
||||
|
||||
// faucetInfos is returned from an faucet status check to allow reporting various
|
||||
@@ -156,15 +147,38 @@ type faucetInfos struct {
|
||||
amount int
|
||||
minutes int
|
||||
tiers int
|
||||
githubUser string
|
||||
githubToken string
|
||||
noauth bool
|
||||
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)
|
||||
// Report converts the typed struct into a plain string->string map, containing
|
||||
// most - but not all - fields for reporting to the user.
|
||||
func (info *faucetInfos) Report() map[string]string {
|
||||
report := map[string]string{
|
||||
"Website address": info.host,
|
||||
"Website listener port": strconv.Itoa(info.port),
|
||||
"Ethereum listener port": strconv.Itoa(info.node.port),
|
||||
"Funding amount (base tier)": fmt.Sprintf("%d Ethers", info.amount),
|
||||
"Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes),
|
||||
"Funding tiers": strconv.Itoa(info.tiers),
|
||||
"Captha protection": fmt.Sprintf("%v", info.captchaToken != ""),
|
||||
"Ethstats username": info.node.ethstats,
|
||||
}
|
||||
if info.noauth {
|
||||
report["Debug mode (no auth)"] = "enabled"
|
||||
}
|
||||
if info.node.keyJSON != "" {
|
||||
var key struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(info.node.keyJSON), &key); err == nil {
|
||||
report["Funding account"] = common.HexToAddress(key.Address).Hex()
|
||||
} else {
|
||||
log.Error("Failed to retrieve signer address", "err", err)
|
||||
}
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
// checkFaucet does a health-check against an faucet server to verify whether
|
||||
@@ -214,7 +228,7 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
||||
return &faucetInfos{
|
||||
node: &nodeInfos{
|
||||
datadir: infos.volumes["/root/.faucet"],
|
||||
portFull: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
|
||||
port: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
|
||||
ethstats: infos.envvars["ETH_NAME"],
|
||||
keyJSON: keyJSON,
|
||||
keyPass: keyPass,
|
||||
@@ -224,9 +238,8 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
||||
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"],
|
||||
noauth: infos.envvars["NO_AUTH"] == "true",
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
@@ -54,7 +55,7 @@ services:
|
||||
// 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) {
|
||||
func deployNginx(client *sshClient, network string, port int, nocache bool) ([]byte, error) {
|
||||
log.Info("Deploying nginx reverse-proxy", "server", client.server, "port", port)
|
||||
|
||||
// Generate the content to upload to the server
|
||||
@@ -78,8 +79,11 @@ func deployNginx(client *sshClient, network string, port int) ([]byte, error) {
|
||||
}
|
||||
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))
|
||||
// Build and deploy the reverse-proxy service
|
||||
if nocache {
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
||||
}
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||
}
|
||||
|
||||
// nginxInfos is returned from an nginx reverse-proxy status check to allow
|
||||
@@ -88,9 +92,12 @@ type nginxInfos struct {
|
||||
port int
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (info *nginxInfos) String() string {
|
||||
return fmt.Sprintf("port=%d", info.port)
|
||||
// Report converts the typed struct into a plain string->string map, containing
|
||||
// most - but not all - fields for reporting to the user.
|
||||
func (info *nginxInfos) Report() map[string]string {
|
||||
return map[string]string{
|
||||
"Shared listener port": strconv.Itoa(info.port),
|
||||
}
|
||||
}
|
||||
|
||||
// checkNginx does a health-check against an nginx reverse-proxy to verify whether
|
||||
|
||||
@@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
@@ -38,9 +40,9 @@ ADD genesis.json /genesis.json
|
||||
ADD signer.pass /signer.pass
|
||||
{{end}}
|
||||
RUN \
|
||||
echo 'geth init /genesis.json' > geth.sh && \{{if .Unlock}}
|
||||
echo 'geth --cache 512 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
|
||||
echo $'geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine --minerthreads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> geth.sh
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
||||
`
|
||||
@@ -54,14 +56,13 @@ services:
|
||||
build: .
|
||||
image: {{.Network}}/{{.Type}}
|
||||
ports:
|
||||
- "{{.FullPort}}:{{.FullPort}}"
|
||||
- "{{.FullPort}}:{{.FullPort}}/udp"{{if .Light}}
|
||||
- "{{.LightPort}}:{{.LightPort}}/udp"{{end}}
|
||||
- "{{.Port}}:{{.Port}}"
|
||||
- "{{.Port}}:{{.Port}}/udp"
|
||||
volumes:
|
||||
- {{.Datadir}}:/root/.ethereum
|
||||
- {{.Datadir}}:/root/.ethereum{{if .Ethashdir}}
|
||||
- {{.Ethashdir}}:/root/.ethash{{end}}
|
||||
environment:
|
||||
- FULL_PORT={{.FullPort}}/tcp
|
||||
- LIGHT_PORT={{.LightPort}}/udp
|
||||
- PORT={{.Port}}/tcp
|
||||
- TOTAL_PEERS={{.TotalPeers}}
|
||||
- LIGHT_PEERS={{.LightPeers}}
|
||||
- STATS_NAME={{.Ethstats}}
|
||||
@@ -79,12 +80,11 @@ services:
|
||||
// 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) {
|
||||
func deployNode(client *sshClient, network string, bootnodes []string, config *nodeInfos, nocache bool) ([]byte, error) {
|
||||
kind := "sealnode"
|
||||
if config.keyJSON == "" && config.etherbase == "" {
|
||||
kind = "bootnode"
|
||||
bootv4 = make([]string, 0)
|
||||
bootv5 = make([]string, 0)
|
||||
bootnodes = make([]string, 0)
|
||||
}
|
||||
// Generate the content to upload to the server
|
||||
workdir := fmt.Sprintf("%d", rand.Int63())
|
||||
@@ -97,11 +97,10 @@ func deployNode(client *sshClient, network string, bootv4, bootv5 []string, conf
|
||||
dockerfile := new(bytes.Buffer)
|
||||
template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||
"NetworkID": config.network,
|
||||
"Port": config.portFull,
|
||||
"Port": config.port,
|
||||
"Peers": config.peersTotal,
|
||||
"LightFlag": lightFlag,
|
||||
"BootV4": strings.Join(bootv4, ","),
|
||||
"BootV5": strings.Join(bootv5, ","),
|
||||
"Bootnodes": strings.Join(bootnodes, ","),
|
||||
"Ethstats": config.ethstats,
|
||||
"Etherbase": config.etherbase,
|
||||
"GasTarget": uint64(1000000 * config.gasTarget),
|
||||
@@ -114,11 +113,11 @@ func deployNode(client *sshClient, network string, bootv4, bootv5 []string, conf
|
||||
template.Must(template.New("").Parse(nodeComposefile)).Execute(composefile, map[string]interface{}{
|
||||
"Type": kind,
|
||||
"Datadir": config.datadir,
|
||||
"Ethashdir": config.ethashdir,
|
||||
"Network": network,
|
||||
"FullPort": config.portFull,
|
||||
"Port": config.port,
|
||||
"TotalPeers": config.peersTotal,
|
||||
"Light": config.peersLight > 0,
|
||||
"LightPort": config.portFull + 1,
|
||||
"LightPeers": config.peersLight,
|
||||
"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
|
||||
"Etherbase": config.etherbase,
|
||||
@@ -127,9 +126,7 @@ func deployNode(client *sshClient, network string, bootv4, bootv5 []string, conf
|
||||
})
|
||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||
|
||||
//genesisfile, _ := json.MarshalIndent(config.genesis, "", " ")
|
||||
files[filepath.Join(workdir, "genesis.json")] = config.genesis
|
||||
|
||||
if config.keyJSON != "" {
|
||||
files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON)
|
||||
files[filepath.Join(workdir, "signer.pass")] = []byte(config.keyPass)
|
||||
@@ -141,7 +138,10 @@ func deployNode(client *sshClient, network string, bootv4, bootv5 []string, conf
|
||||
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))
|
||||
if nocache {
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
||||
}
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||
}
|
||||
|
||||
// nodeInfos is returned from a boot or seal node status check to allow reporting
|
||||
@@ -150,11 +150,10 @@ type nodeInfos struct {
|
||||
genesis []byte
|
||||
network int64
|
||||
datadir string
|
||||
ethashdir string
|
||||
ethstats string
|
||||
portFull int
|
||||
portLight int
|
||||
enodeFull string
|
||||
enodeLight string
|
||||
port int
|
||||
enode string
|
||||
peersTotal int
|
||||
peersLight int
|
||||
etherbase string
|
||||
@@ -164,14 +163,39 @@ type nodeInfos struct {
|
||||
gasPrice float64
|
||||
}
|
||||
|
||||
// String implements the stringer interface.
|
||||
func (info *nodeInfos) String() string {
|
||||
discv5 := ""
|
||||
if info.peersLight > 0 {
|
||||
discv5 = fmt.Sprintf(", portv5=%d", info.portLight)
|
||||
// Report converts the typed struct into a plain string->string map, containing
|
||||
// most - but not all - fields for reporting to the user.
|
||||
func (info *nodeInfos) Report() map[string]string {
|
||||
report := map[string]string{
|
||||
"Data directory": info.datadir,
|
||||
"Listener port": strconv.Itoa(info.port),
|
||||
"Peer count (all total)": strconv.Itoa(info.peersTotal),
|
||||
"Peer count (light nodes)": strconv.Itoa(info.peersLight),
|
||||
"Ethstats username": info.ethstats,
|
||||
}
|
||||
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)
|
||||
if info.gasTarget > 0 {
|
||||
// Miner or signer node
|
||||
report["Gas limit (baseline target)"] = fmt.Sprintf("%0.3f MGas", info.gasTarget)
|
||||
report["Gas price (minimum accepted)"] = fmt.Sprintf("%0.3f GWei", info.gasPrice)
|
||||
|
||||
if info.etherbase != "" {
|
||||
// Ethash proof-of-work miner
|
||||
report["Ethash directory"] = info.ethashdir
|
||||
report["Miner account"] = info.etherbase
|
||||
}
|
||||
if info.keyJSON != "" {
|
||||
// Clique proof-of-authority signer
|
||||
var key struct {
|
||||
Address string `json:"address"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(info.keyJSON), &key); err == nil {
|
||||
report["Signer account"] = common.HexToAddress(key.Address).Hex()
|
||||
} else {
|
||||
log.Error("Failed to retrieve signer address", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
// checkNode does a health-check against an boot or seal node server to verify
|
||||
@@ -215,7 +239,7 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||
keyPass = string(bytes.TrimSpace(out))
|
||||
}
|
||||
// Run a sanity check to see if the devp2p is reachable
|
||||
port := infos.portmap[infos.envvars["FULL_PORT"]]
|
||||
port := infos.portmap[infos.envvars["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)
|
||||
}
|
||||
@@ -223,8 +247,8 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||
stats := &nodeInfos{
|
||||
genesis: genesis,
|
||||
datadir: infos.volumes["/root/.ethereum"],
|
||||
portFull: infos.portmap[infos.envvars["FULL_PORT"]],
|
||||
portLight: infos.portmap[infos.envvars["LIGHT_PORT"]],
|
||||
ethashdir: infos.volumes["/root/.ethash"],
|
||||
port: port,
|
||||
peersTotal: totalPeers,
|
||||
peersLight: lightPeers,
|
||||
ethstats: infos.envvars["STATS_NAME"],
|
||||
@@ -234,9 +258,7 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||
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)
|
||||
}
|
||||
stats.enode = fmt.Sprintf("enode://%s@%s:%d", id, client.address, stats.port)
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
200
cmd/puppeth/module_wallet.go
Normal file
200
cmd/puppeth/module_wallet.go
Normal file
@@ -0,0 +1,200 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// walletDockerfile is the Dockerfile required to run a web wallet.
|
||||
var walletDockerfile = `
|
||||
FROM puppeth/wallet:latest
|
||||
|
||||
ADD genesis.json /genesis.json
|
||||
|
||||
RUN \
|
||||
echo 'node server.js &' > wallet.sh && \
|
||||
echo 'geth --cache 512 init /genesis.json' >> wallet.sh && \
|
||||
echo $'geth --networkid {{.NetworkID}} --port {{.NodePort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --rpc --rpcaddr=0.0.0.0 --rpccorsdomain "*"' >> wallet.sh
|
||||
|
||||
RUN \
|
||||
sed -i 's/PuppethNetworkID/{{.NetworkID}}/g' dist/js/etherwallet-master.js && \
|
||||
sed -i 's/PuppethNetwork/{{.Network}}/g' dist/js/etherwallet-master.js && \
|
||||
sed -i 's/PuppethDenom/{{.Denom}}/g' dist/js/etherwallet-master.js && \
|
||||
sed -i 's/PuppethHost/{{.Host}}/g' dist/js/etherwallet-master.js && \
|
||||
sed -i 's/PuppethRPCPort/{{.RPCPort}}/g' dist/js/etherwallet-master.js
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "wallet.sh"]
|
||||
`
|
||||
|
||||
// walletComposefile is the docker-compose.yml file required to deploy and
|
||||
// maintain a web wallet.
|
||||
var walletComposefile = `
|
||||
version: '2'
|
||||
services:
|
||||
wallet:
|
||||
build: .
|
||||
image: {{.Network}}/wallet
|
||||
ports:
|
||||
- "{{.NodePort}}:{{.NodePort}}"
|
||||
- "{{.NodePort}}:{{.NodePort}}/udp"
|
||||
- "{{.RPCPort}}:8545"{{if not .VHost}}
|
||||
- "{{.WebPort}}:80"{{end}}
|
||||
volumes:
|
||||
- {{.Datadir}}:/root/.ethereum
|
||||
environment:
|
||||
- NODE_PORT={{.NodePort}}/tcp
|
||||
- STATS={{.Ethstats}}{{if .VHost}}
|
||||
- VIRTUAL_HOST={{.VHost}}
|
||||
- VIRTUAL_PORT=80{{end}}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "1m"
|
||||
max-file: "10"
|
||||
restart: always
|
||||
`
|
||||
|
||||
// deployWallet deploys a new web wallet 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 deployWallet(client *sshClient, network string, bootnodes []string, config *walletInfos, nocache bool) ([]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(walletDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||
"Network": strings.ToTitle(network),
|
||||
"Denom": strings.ToUpper(network),
|
||||
"NetworkID": config.network,
|
||||
"NodePort": config.nodePort,
|
||||
"RPCPort": config.rpcPort,
|
||||
"Bootnodes": strings.Join(bootnodes, ","),
|
||||
"Ethstats": config.ethstats,
|
||||
"Host": client.address,
|
||||
})
|
||||
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||
|
||||
composefile := new(bytes.Buffer)
|
||||
template.Must(template.New("").Parse(walletComposefile)).Execute(composefile, map[string]interface{}{
|
||||
"Datadir": config.datadir,
|
||||
"Network": network,
|
||||
"NodePort": config.nodePort,
|
||||
"RPCPort": config.rpcPort,
|
||||
"VHost": config.webHost,
|
||||
"WebPort": config.webPort,
|
||||
"Ethstats": config.ethstats[:strings.Index(config.ethstats, ":")],
|
||||
})
|
||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||
|
||||
files[filepath.Join(workdir, "genesis.json")] = config.genesis
|
||||
|
||||
// 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
|
||||
if nocache {
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate", workdir, network, network))
|
||||
}
|
||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||
}
|
||||
|
||||
// walletInfos is returned from a web wallet status check to allow reporting
|
||||
// various configuration parameters.
|
||||
type walletInfos struct {
|
||||
genesis []byte
|
||||
network int64
|
||||
datadir string
|
||||
ethstats string
|
||||
nodePort int
|
||||
rpcPort int
|
||||
webHost string
|
||||
webPort int
|
||||
}
|
||||
|
||||
// Report converts the typed struct into a plain string->string map, containing
|
||||
// most - but not all - fields for reporting to the user.
|
||||
func (info *walletInfos) Report() map[string]string {
|
||||
report := map[string]string{
|
||||
"Data directory": info.datadir,
|
||||
"Ethstats username": info.ethstats,
|
||||
"Node listener port ": strconv.Itoa(info.nodePort),
|
||||
"RPC listener port ": strconv.Itoa(info.rpcPort),
|
||||
"Website address ": info.webHost,
|
||||
"Website listener port ": strconv.Itoa(info.webPort),
|
||||
}
|
||||
return report
|
||||
}
|
||||
|
||||
// checkWallet does a health-check against web wallet server to verify whether
|
||||
// it's running, and if yes, whether it's responsive.
|
||||
func checkWallet(client *sshClient, network string) (*walletInfos, error) {
|
||||
// Inspect a possible web wallet container on the host
|
||||
infos, err := inspectContainer(client, fmt.Sprintf("%s_wallet_1", network))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !infos.running {
|
||||
return nil, ErrServiceOffline
|
||||
}
|
||||
// Resolve the port from the host, or the reverse proxy
|
||||
webPort := infos.portmap["80/tcp"]
|
||||
if webPort == 0 {
|
||||
if proxy, _ := checkNginx(client, network); proxy != nil {
|
||||
webPort = proxy.port
|
||||
}
|
||||
}
|
||||
if webPort == 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
|
||||
}
|
||||
// Run a sanity check to see if the devp2p and RPC ports are reachable
|
||||
nodePort := infos.portmap[infos.envvars["NODE_PORT"]]
|
||||
if err = checkPort(client.server, nodePort); err != nil {
|
||||
log.Warn(fmt.Sprintf("Wallet devp2p port seems unreachable"), "server", client.server, "port", nodePort, "err", err)
|
||||
}
|
||||
rpcPort := infos.portmap["8545/tcp"]
|
||||
if err = checkPort(client.server, rpcPort); err != nil {
|
||||
log.Warn(fmt.Sprintf("Wallet RPC port seems unreachable"), "server", client.server, "port", rpcPort, "err", err)
|
||||
}
|
||||
// Assemble and return the useful infos
|
||||
stats := &walletInfos{
|
||||
datadir: infos.volumes["/root/.ethereum"],
|
||||
nodePort: nodePort,
|
||||
rpcPort: rpcPort,
|
||||
webHost: host,
|
||||
webPort: webPort,
|
||||
ethstats: infos.envvars["STATS"],
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func main() {
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "loglevel",
|
||||
Value: 4,
|
||||
Value: 3,
|
||||
Usage: "log level to emit to the screen",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||
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.Println()
|
||||
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)? ")
|
||||
@@ -215,8 +216,8 @@ func (client *sshClient) Stream(cmd string) error {
|
||||
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.
|
||||
// Upload copies the set of files to a remote server via SCP, creating any non-
|
||||
// existing folders in the mean time.
|
||||
func (client *sshClient) Upload(files map[string][]byte) ([]byte, error) {
|
||||
// Establish a single command session
|
||||
session, err := client.client.NewSession()
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@@ -38,12 +39,11 @@ import (
|
||||
// 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
|
||||
path string // File containing the configuration values
|
||||
bootnodes []string // Bootnodes to always connect to by all nodes
|
||||
ethstats string // Ethstats settings to cache for node deploys
|
||||
|
||||
Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys
|
||||
Servers map[string][]byte `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ type wizard struct {
|
||||
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
|
||||
in *bufio.Reader // Wrapper around stdin to allow reading user input
|
||||
lock sync.Mutex // Lock to protect configs during concurrent service discovery
|
||||
}
|
||||
|
||||
// read reads a single line from stdin, trimming if from spaces.
|
||||
|
||||
@@ -40,6 +40,8 @@ func (w *wizard) deployDashboard() {
|
||||
host: client.server,
|
||||
}
|
||||
}
|
||||
existed := err == nil
|
||||
|
||||
// Figure out which port to listen on
|
||||
fmt.Println()
|
||||
fmt.Printf("Which port should the dashboard listen on? (default = %d)\n", infos.port)
|
||||
@@ -58,7 +60,6 @@ func (w *wizard) deployDashboard() {
|
||||
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
|
||||
@@ -74,6 +75,14 @@ func (w *wizard) deployDashboard() {
|
||||
if infos, err := checkEthstats(client, w.network); err == nil {
|
||||
port = infos.port
|
||||
}
|
||||
case "explorer":
|
||||
if infos, err := checkExplorer(client, w.network); err == nil {
|
||||
port = infos.webPort
|
||||
}
|
||||
case "wallet":
|
||||
if infos, err := checkWallet(client, w.network); err == nil {
|
||||
port = infos.webPort
|
||||
}
|
||||
case "faucet":
|
||||
if infos, err := checkFaucet(client, w.network); err == nil {
|
||||
port = infos.port
|
||||
@@ -101,26 +110,43 @@ func (w *wizard) deployDashboard() {
|
||||
log.Error("Invalid listing choice, aborting")
|
||||
return
|
||||
}
|
||||
var page string
|
||||
switch {
|
||||
case choice <= len(pages):
|
||||
listing[service] = pages[choice-1]
|
||||
page = 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()
|
||||
page = w.readString()
|
||||
default:
|
||||
// No service hosting for this
|
||||
}
|
||||
// Save the users choice
|
||||
switch service {
|
||||
case "ethstats":
|
||||
infos.ethstats = page
|
||||
case "explorer":
|
||||
infos.explorer = page
|
||||
case "wallet":
|
||||
infos.wallet = page
|
||||
case "faucet":
|
||||
infos.faucet = page
|
||||
}
|
||||
}
|
||||
// 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"
|
||||
infos.trusted = 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 {
|
||||
nocache := false
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the dashboard be built from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
if out, err := deployDashboard(client, w.network, &w.conf, infos, nocache); err != nil {
|
||||
log.Error("Failed to deploy dashboard container", "err", err)
|
||||
if len(out) > 0 {
|
||||
fmt.Printf("%s\n", out)
|
||||
@@ -128,5 +154,5 @@ func (w *wizard) deployDashboard() {
|
||||
return
|
||||
}
|
||||
// All ok, run a network scan to pick any changes up
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ func (w *wizard) deployEthstats() {
|
||||
secret: "",
|
||||
}
|
||||
}
|
||||
existed := err == nil
|
||||
|
||||
// Figure out which port to listen on
|
||||
fmt.Println()
|
||||
fmt.Printf("Which port should ethstats listen on? (default = %d)\n", infos.port)
|
||||
@@ -62,49 +64,57 @@ func (w *wizard) deployEthstats() {
|
||||
infos.secret = w.readDefaultString(infos.secret)
|
||||
}
|
||||
// Gather any blacklists to ban from reporting
|
||||
fmt.Println()
|
||||
fmt.Printf("Keep existing IP %v blacklist (y/n)? (default = yes)\n", infos.banned)
|
||||
if w.readDefaultString("y") != "y" {
|
||||
// The user might want to clear the entire list, although generally probably not
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Clear out blacklist and start over (y/n)? (default = no)\n")
|
||||
if w.readDefaultString("n") != "n" {
|
||||
infos.banned = nil
|
||||
}
|
||||
// Offer the user to explicitly add/remove certain IP addresses
|
||||
fmt.Println()
|
||||
fmt.Println("Which additional IP addresses should be blacklisted?")
|
||||
for {
|
||||
if ip := w.readIPAddress(); ip != "" {
|
||||
infos.banned = append(infos.banned, ip)
|
||||
continue
|
||||
fmt.Printf("Keep existing IP %v blacklist (y/n)? (default = yes)\n", infos.banned)
|
||||
if w.readDefaultString("y") != "y" {
|
||||
// The user might want to clear the entire list, although generally probably not
|
||||
fmt.Println()
|
||||
fmt.Printf("Clear out blacklist and start over (y/n)? (default = no)\n")
|
||||
if w.readDefaultString("n") != "n" {
|
||||
infos.banned = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("Which IP addresses should not be blacklisted?")
|
||||
for {
|
||||
if ip := w.readIPAddress(); ip != "" {
|
||||
for i, addr := range infos.banned {
|
||||
if ip == addr {
|
||||
infos.banned = append(infos.banned[:i], infos.banned[i+1:]...)
|
||||
break
|
||||
}
|
||||
// Offer the user to explicitly add/remove certain IP addresses
|
||||
fmt.Println()
|
||||
fmt.Println("Which additional IP addresses should be blacklisted?")
|
||||
for {
|
||||
if ip := w.readIPAddress(); ip != "" {
|
||||
infos.banned = append(infos.banned, ip)
|
||||
continue
|
||||
}
|
||||
continue
|
||||
break
|
||||
}
|
||||
break
|
||||
fmt.Println()
|
||||
fmt.Println("Which IP addresses should not be blacklisted?")
|
||||
for {
|
||||
if ip := w.readIPAddress(); ip != "" {
|
||||
for i, addr := range infos.banned {
|
||||
if ip == addr {
|
||||
infos.banned = append(infos.banned[:i], infos.banned[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
sort.Strings(infos.banned)
|
||||
}
|
||||
sort.Strings(infos.banned)
|
||||
}
|
||||
// Try to deploy the ethstats server on the host
|
||||
nocache := false
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the ethstats be built from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
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, infos.banned); err != nil {
|
||||
if out, err := deployEthstats(client, w.network, infos.port, infos.secret, infos.host, trusted, infos.banned, nocache); err != nil {
|
||||
log.Error("Failed to deploy ethstats container", "err", err)
|
||||
if len(out) > 0 {
|
||||
fmt.Printf("%s\n", out)
|
||||
@@ -112,5 +122,5 @@ func (w *wizard) deployEthstats() {
|
||||
return
|
||||
}
|
||||
// All ok, run a network scan to pick any changes up
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
}
|
||||
|
||||
117
cmd/puppeth/wizard_explorer.go
Normal file
117
cmd/puppeth/wizard_explorer.go
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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/log"
|
||||
)
|
||||
|
||||
// deployExplorer creates a new block explorer based on some user input.
|
||||
func (w *wizard) deployExplorer() {
|
||||
// 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
|
||||
}
|
||||
if w.conf.Genesis.Config.Ethash == nil {
|
||||
log.Error("Only ethash network supported")
|
||||
return
|
||||
}
|
||||
// Select the server to interact with
|
||||
server := w.selectServer()
|
||||
if server == "" {
|
||||
return
|
||||
}
|
||||
client := w.servers[server]
|
||||
|
||||
// Retrieve any active node configurations from the server
|
||||
infos, err := checkExplorer(client, w.network)
|
||||
if err != nil {
|
||||
infos = &explorerInfos{
|
||||
nodePort: 30303, webPort: 80, webHost: client.server,
|
||||
}
|
||||
}
|
||||
existed := err == nil
|
||||
|
||||
chainspec, err := newParityChainSpec(w.network, w.conf.Genesis, w.conf.bootnodes)
|
||||
if err != nil {
|
||||
log.Error("Failed to create chain spec for explorer", "err", err)
|
||||
return
|
||||
}
|
||||
chain, _ := json.MarshalIndent(chainspec, "", " ")
|
||||
|
||||
// Figure out which port to listen on
|
||||
fmt.Println()
|
||||
fmt.Printf("Which port should the explorer listen on? (default = %d)\n", infos.webPort)
|
||||
infos.webPort = w.readDefaultInt(infos.webPort)
|
||||
|
||||
// Figure which virtual-host to deploy ethstats on
|
||||
if infos.webHost, err = w.ensureVirtualHost(client, infos.webPort, infos.webHost); err != nil {
|
||||
log.Error("Failed to decide on explorer host", "err", err)
|
||||
return
|
||||
}
|
||||
// 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 should the archive node listen on? (default = %d)\n", infos.nodePort)
|
||||
infos.nodePort = w.readDefaultInt(infos.nodePort)
|
||||
|
||||
// Set a proper name to report on the stats page
|
||||
fmt.Println()
|
||||
if infos.ethstats == "" {
|
||||
fmt.Printf("What should the explorer be called on the stats page?\n")
|
||||
infos.ethstats = w.readString() + ":" + w.conf.ethstats
|
||||
} else {
|
||||
fmt.Printf("What should the explorer be called on the stats page? (default = %s)\n", infos.ethstats)
|
||||
infos.ethstats = w.readDefaultString(infos.ethstats) + ":" + w.conf.ethstats
|
||||
}
|
||||
// Try to deploy the explorer on the host
|
||||
nocache := false
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the explorer be built from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
if out, err := deployExplorer(client, w.network, chain, infos, nocache); err != nil {
|
||||
log.Error("Failed to deploy explorer 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()
|
||||
}
|
||||
@@ -19,7 +19,6 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -39,7 +38,7 @@ func (w *wizard) deployFaucet() {
|
||||
infos, err := checkFaucet(client, w.network)
|
||||
if err != nil {
|
||||
infos = &faucetInfos{
|
||||
node: &nodeInfos{portFull: 30303, peersTotal: 25},
|
||||
node: &nodeInfos{port: 30303, peersTotal: 25},
|
||||
port: 80,
|
||||
host: client.server,
|
||||
amount: 1,
|
||||
@@ -47,8 +46,10 @@ func (w *wizard) deployFaucet() {
|
||||
tiers: 3,
|
||||
}
|
||||
}
|
||||
infos.node.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ")
|
||||
infos.node.network = w.conf.genesis.Config.ChainId.Int64()
|
||||
existed := err == nil
|
||||
|
||||
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()
|
||||
@@ -60,7 +61,7 @@ func (w *wizard) deployFaucet() {
|
||||
log.Error("Failed to decide on faucet host", "err", err)
|
||||
return
|
||||
}
|
||||
// Port and proxy settings retrieved, figure out the funcing amount per perdion configurations
|
||||
// Port and proxy settings retrieved, figure out the funding amount per period configurations
|
||||
fmt.Println()
|
||||
fmt.Printf("How many Ethers to release per request? (default = %d)\n", infos.amount)
|
||||
infos.amount = w.readDefaultInt(infos.amount)
|
||||
@@ -76,47 +77,6 @@ func (w *wizard) deployFaucet() {
|
||||
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()
|
||||
@@ -129,7 +89,9 @@ func (w *wizard) deployFaucet() {
|
||||
// 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" {
|
||||
if w.readDefaultString("n") == "n" {
|
||||
log.Warn("Users will be able to requests funds via automated scripts")
|
||||
} else {
|
||||
// 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")
|
||||
@@ -151,8 +113,8 @@ func (w *wizard) deployFaucet() {
|
||||
}
|
||||
// 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)
|
||||
fmt.Printf("Which TCP/UDP port should the light client listen on? (default = %d)\n", infos.node.port)
|
||||
infos.node.port = w.readDefaultInt(infos.node.port)
|
||||
|
||||
// Set a proper name to report on the stats page
|
||||
fmt.Println()
|
||||
@@ -175,7 +137,7 @@ func (w *wizard) deployFaucet() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if infos.node.keyJSON == "" {
|
||||
for i := 0; i < 3 && infos.node.keyJSON == ""; i++ {
|
||||
fmt.Println()
|
||||
fmt.Println("Please paste the faucet's funding account key JSON:")
|
||||
infos.node.keyJSON = w.readJSON()
|
||||
@@ -186,11 +148,27 @@ func (w *wizard) deployFaucet() {
|
||||
|
||||
if _, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
|
||||
log.Error("Failed to decrypt key with given passphrase")
|
||||
return
|
||||
infos.node.keyJSON = ""
|
||||
infos.node.keyPass = ""
|
||||
}
|
||||
}
|
||||
// Check if the user wants to run the faucet in debug mode (noauth)
|
||||
noauth := "n"
|
||||
if infos.noauth {
|
||||
noauth = "y"
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("Permit non-authenticated funding requests (y/n)? (default = %v)\n", infos.noauth)
|
||||
infos.noauth = w.readDefaultString(noauth) != "n"
|
||||
|
||||
// Try to deploy the faucet server on the host
|
||||
if out, err := deployFaucet(client, w.network, w.conf.bootLight, infos); err != nil {
|
||||
nocache := false
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the faucet be built from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
if out, err := deployFaucet(client, w.network, w.conf.bootnodes, infos, nocache); err != nil {
|
||||
log.Error("Failed to deploy faucet container", "err", err)
|
||||
if len(out) > 0 {
|
||||
fmt.Printf("%s\n", out)
|
||||
@@ -198,5 +176,5 @@ func (w *wizard) deployFaucet() {
|
||||
return
|
||||
}
|
||||
// All ok, run a network scan to pick any changes up
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func (w *wizard) makeGenesis() {
|
||||
genesis := &core.Genesis{
|
||||
Timestamp: uint64(time.Now().Unix()),
|
||||
GasLimit: 4700000,
|
||||
Difficulty: big.NewInt(1048576),
|
||||
Difficulty: big.NewInt(524288),
|
||||
Alloc: make(core.GenesisAlloc),
|
||||
Config: ¶ms.ChainConfig{
|
||||
HomesteadBlock: big.NewInt(1),
|
||||
@@ -118,24 +118,16 @@ func (w *wizard) makeGenesis() {
|
||||
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
|
||||
log.Info("Configured new genesis block")
|
||||
|
||||
w.conf.Genesis = genesis
|
||||
w.conf.flush()
|
||||
}
|
||||
|
||||
// manageGenesis permits the modification of chain configuration parameters in
|
||||
@@ -145,44 +137,56 @@ func (w *wizard) manageGenesis() {
|
||||
fmt.Println()
|
||||
fmt.Println(" 1. Modify existing fork rules")
|
||||
fmt.Println(" 2. Export genesis configuration")
|
||||
fmt.Println(" 3. Remove genesis configuration")
|
||||
|
||||
choice := w.read()
|
||||
switch {
|
||||
case choice == "1":
|
||||
// Fork rule updating requested, iterate over each fork
|
||||
fmt.Println()
|
||||
fmt.Printf("Which block should Homestead come into effect? (default = %v)\n", w.conf.genesis.Config.HomesteadBlock)
|
||||
w.conf.genesis.Config.HomesteadBlock = w.readDefaultBigInt(w.conf.genesis.Config.HomesteadBlock)
|
||||
fmt.Printf("Which block should Homestead come into effect? (default = %v)\n", w.conf.Genesis.Config.HomesteadBlock)
|
||||
w.conf.Genesis.Config.HomesteadBlock = w.readDefaultBigInt(w.conf.Genesis.Config.HomesteadBlock)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Which block should EIP150 come into effect? (default = %v)\n", w.conf.genesis.Config.EIP150Block)
|
||||
w.conf.genesis.Config.EIP150Block = w.readDefaultBigInt(w.conf.genesis.Config.EIP150Block)
|
||||
fmt.Printf("Which block should EIP150 come into effect? (default = %v)\n", w.conf.Genesis.Config.EIP150Block)
|
||||
w.conf.Genesis.Config.EIP150Block = w.readDefaultBigInt(w.conf.Genesis.Config.EIP150Block)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Which block should EIP155 come into effect? (default = %v)\n", w.conf.genesis.Config.EIP155Block)
|
||||
w.conf.genesis.Config.EIP155Block = w.readDefaultBigInt(w.conf.genesis.Config.EIP155Block)
|
||||
fmt.Printf("Which block should EIP155 come into effect? (default = %v)\n", w.conf.Genesis.Config.EIP155Block)
|
||||
w.conf.Genesis.Config.EIP155Block = w.readDefaultBigInt(w.conf.Genesis.Config.EIP155Block)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Which block should EIP158 come into effect? (default = %v)\n", w.conf.genesis.Config.EIP158Block)
|
||||
w.conf.genesis.Config.EIP158Block = w.readDefaultBigInt(w.conf.genesis.Config.EIP158Block)
|
||||
fmt.Printf("Which block should EIP158 come into effect? (default = %v)\n", w.conf.Genesis.Config.EIP158Block)
|
||||
w.conf.Genesis.Config.EIP158Block = w.readDefaultBigInt(w.conf.Genesis.Config.EIP158Block)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Which block should Byzantium come into effect? (default = %v)\n", w.conf.genesis.Config.ByzantiumBlock)
|
||||
w.conf.genesis.Config.ByzantiumBlock = w.readDefaultBigInt(w.conf.genesis.Config.ByzantiumBlock)
|
||||
fmt.Printf("Which block should Byzantium come into effect? (default = %v)\n", w.conf.Genesis.Config.ByzantiumBlock)
|
||||
w.conf.Genesis.Config.ByzantiumBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ByzantiumBlock)
|
||||
|
||||
out, _ := json.MarshalIndent(w.conf.genesis.Config, "", " ")
|
||||
out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ")
|
||||
fmt.Printf("Chain configuration updated:\n\n%s\n", out)
|
||||
|
||||
case choice == "2":
|
||||
// Save whatever genesis configuration 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, "", " ")
|
||||
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":
|
||||
// Make sure we don't have any services running
|
||||
if len(w.conf.servers()) > 0 {
|
||||
log.Error("Genesis reset requires all services and servers torn down")
|
||||
return
|
||||
}
|
||||
log.Info("Genesis block destroyed")
|
||||
|
||||
w.conf.Genesis = nil
|
||||
w.conf.flush()
|
||||
|
||||
default:
|
||||
log.Error("That's not something I can do")
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
@@ -63,7 +64,7 @@ func (w *wizard) run() {
|
||||
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)
|
||||
fmt.Printf("\nSweet, 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")
|
||||
@@ -80,22 +81,33 @@ func (w *wizard) run() {
|
||||
} else if err := json.Unmarshal(blob, &w.conf); err != nil {
|
||||
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
|
||||
} else {
|
||||
// Dial all previously known servers concurrently
|
||||
var pend sync.WaitGroup
|
||||
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
|
||||
pend.Add(1)
|
||||
|
||||
go func(server string, pubkey []byte) {
|
||||
defer pend.Done()
|
||||
|
||||
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.lock.Lock()
|
||||
w.servers[server] = client
|
||||
w.lock.Unlock()
|
||||
}(server, pubkey)
|
||||
}
|
||||
w.networkStats(false)
|
||||
pend.Wait()
|
||||
w.networkStats()
|
||||
}
|
||||
// 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 {
|
||||
if w.conf.Genesis == nil {
|
||||
fmt.Println(" 2. Configure new genesis")
|
||||
} else {
|
||||
fmt.Println(" 2. Manage existing genesis")
|
||||
@@ -110,15 +122,14 @@ func (w *wizard) run() {
|
||||
} 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)
|
||||
w.networkStats()
|
||||
|
||||
case choice == "2":
|
||||
if w.conf.genesis == nil {
|
||||
if w.conf.Genesis == nil {
|
||||
w.makeGenesis()
|
||||
} else {
|
||||
w.manageGenesis()
|
||||
@@ -126,7 +137,7 @@ func (w *wizard) run() {
|
||||
case choice == "3":
|
||||
if len(w.servers) == 0 {
|
||||
if w.makeServer() != "" {
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
}
|
||||
} else {
|
||||
w.manageServers()
|
||||
@@ -138,9 +149,6 @@ func (w *wizard) run() {
|
||||
w.manageComponents()
|
||||
}
|
||||
|
||||
case choice == "5":
|
||||
w.networkStats(true)
|
||||
|
||||
default:
|
||||
log.Error("That's not something I can do")
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
@@ -29,127 +30,251 @@ import (
|
||||
|
||||
// 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) {
|
||||
func (w *wizard) networkStats() {
|
||||
if len(w.servers) == 0 {
|
||||
log.Error("No remote machines to gather stats from")
|
||||
log.Info("No remote machines to gather stats from")
|
||||
return
|
||||
}
|
||||
protips := new(protips)
|
||||
// Clear out some previous configs to refill from current scan
|
||||
w.conf.ethstats = ""
|
||||
w.conf.bootnodes = w.conf.bootnodes[:0]
|
||||
|
||||
// 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)
|
||||
var pend sync.WaitGroup
|
||||
|
||||
stats := make(serverStats)
|
||||
for server, pubkey := range w.conf.Servers {
|
||||
client := w.servers[server]
|
||||
logger := log.New("server", server)
|
||||
logger.Info("Starting remote server health-check")
|
||||
pend.Add(1)
|
||||
|
||||
// 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()
|
||||
// Gather the service stats for each server concurrently
|
||||
go func(server string, pubkey []byte) {
|
||||
defer pend.Done()
|
||||
|
||||
protips.genesis = string(infos.genesis)
|
||||
protips.bootFull = append(protips.bootFull, infos.enodeFull)
|
||||
if infos.enodeLight != "" {
|
||||
protips.bootLight = append(protips.bootLight, infos.enodeLight)
|
||||
stat := w.gatherStats(server, pubkey, w.servers[server])
|
||||
|
||||
// All status checks complete, report and check next server
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
delete(w.services, server)
|
||||
for service := range stat.services {
|
||||
w.services[server] = append(w.services[server], service)
|
||||
}
|
||||
}
|
||||
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", "", ""})
|
||||
}
|
||||
stats[server] = stat
|
||||
}(server, pubkey)
|
||||
}
|
||||
// If a genesis block was found, load it into our configs
|
||||
if protips.genesis != "" && w.conf.genesis == nil {
|
||||
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
|
||||
pend.Wait()
|
||||
|
||||
// Print any collected stats and return
|
||||
if !tips {
|
||||
stats.Render()
|
||||
} else {
|
||||
protips.print(w.network)
|
||||
stats.render()
|
||||
}
|
||||
|
||||
// gatherStats gathers service statistics for a particular remote server.
|
||||
func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *serverStat {
|
||||
// Gather some global stats to feed into the wizard
|
||||
var (
|
||||
genesis string
|
||||
ethstats string
|
||||
bootnodes []string
|
||||
)
|
||||
// Ensure a valid SSH connection to the remote server
|
||||
logger := log.New("server", server)
|
||||
logger.Info("Starting remote server health-check")
|
||||
|
||||
stat := &serverStat{
|
||||
address: client.address,
|
||||
services: make(map[string]map[string]string),
|
||||
}
|
||||
if client == nil {
|
||||
conn, err := dial(server, pubkey)
|
||||
if err != nil {
|
||||
logger.Error("Failed to establish remote connection", "err", err)
|
||||
stat.failure = err.Error()
|
||||
return stat
|
||||
}
|
||||
client = conn
|
||||
}
|
||||
// Client connected one way or another, run health-checks
|
||||
logger.Debug("Checking for nginx availability")
|
||||
if infos, err := checkNginx(client, w.network); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["nginx"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["nginx"] = infos.Report()
|
||||
}
|
||||
logger.Debug("Checking for ethstats availability")
|
||||
if infos, err := checkEthstats(client, w.network); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["ethstats"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["ethstats"] = infos.Report()
|
||||
ethstats = infos.config
|
||||
}
|
||||
logger.Debug("Checking for bootnode availability")
|
||||
if infos, err := checkNode(client, w.network, true); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["bootnode"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["bootnode"] = infos.Report()
|
||||
|
||||
genesis = string(infos.genesis)
|
||||
bootnodes = append(bootnodes, infos.enode)
|
||||
}
|
||||
logger.Debug("Checking for sealnode availability")
|
||||
if infos, err := checkNode(client, w.network, false); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["sealnode"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["sealnode"] = infos.Report()
|
||||
genesis = string(infos.genesis)
|
||||
}
|
||||
logger.Debug("Checking for explorer availability")
|
||||
if infos, err := checkExplorer(client, w.network); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["explorer"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["explorer"] = infos.Report()
|
||||
}
|
||||
logger.Debug("Checking for wallet availability")
|
||||
if infos, err := checkWallet(client, w.network); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["wallet"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["wallet"] = infos.Report()
|
||||
}
|
||||
logger.Debug("Checking for faucet availability")
|
||||
if infos, err := checkFaucet(client, w.network); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["faucet"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["faucet"] = infos.Report()
|
||||
}
|
||||
logger.Debug("Checking for dashboard availability")
|
||||
if infos, err := checkDashboard(client, w.network); err != nil {
|
||||
if err != ErrServiceUnknown {
|
||||
stat.services["dashboard"] = map[string]string{"offline": err.Error()}
|
||||
}
|
||||
} else {
|
||||
stat.services["dashboard"] = infos.Report()
|
||||
}
|
||||
// Feed and newly discovered information into the wizard
|
||||
w.lock.Lock()
|
||||
defer w.lock.Unlock()
|
||||
|
||||
if genesis != "" && w.conf.Genesis == nil {
|
||||
g := new(core.Genesis)
|
||||
if err := json.Unmarshal([]byte(genesis), g); err != nil {
|
||||
log.Error("Failed to parse remote genesis", "err", err)
|
||||
} else {
|
||||
w.conf.Genesis = g
|
||||
}
|
||||
}
|
||||
if ethstats != "" {
|
||||
w.conf.ethstats = ethstats
|
||||
}
|
||||
w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...)
|
||||
|
||||
return stat
|
||||
}
|
||||
|
||||
// serverStat is a collection of service configuration parameters and health
|
||||
// check reports to print to the user.
|
||||
type serverStat struct {
|
||||
address string
|
||||
failure string
|
||||
services map[string]map[string]string
|
||||
}
|
||||
|
||||
// serverStats is a collection of server stats for multiple hosts.
|
||||
type serverStats map[string]*serverStat
|
||||
|
||||
// render converts the gathered statistics into a user friendly tabular report
|
||||
// and prints it to the standard output.
|
||||
func (stats serverStats) render() {
|
||||
// Start gathering service statistics and config parameters
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
|
||||
table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetColWidth(100)
|
||||
|
||||
// Find the longest lines for all columns for the hacked separator
|
||||
separator := make([]string, 5)
|
||||
for server, stat := range stats {
|
||||
if len(server) > len(separator[0]) {
|
||||
separator[0] = strings.Repeat("-", len(server))
|
||||
}
|
||||
if len(stat.address) > len(separator[1]) {
|
||||
separator[1] = strings.Repeat("-", len(stat.address))
|
||||
}
|
||||
for service, configs := range stat.services {
|
||||
if len(service) > len(separator[2]) {
|
||||
separator[2] = strings.Repeat("-", len(service))
|
||||
}
|
||||
for config, value := range configs {
|
||||
if len(config) > len(separator[3]) {
|
||||
separator[3] = strings.Repeat("-", len(config))
|
||||
}
|
||||
if len(value) > len(separator[4]) {
|
||||
separator[4] = strings.Repeat("-", len(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fill up the server report in alphabetical order
|
||||
servers := make([]string, 0, len(stats))
|
||||
for server := range stats {
|
||||
servers = append(servers, server)
|
||||
}
|
||||
sort.Strings(servers)
|
||||
|
||||
for i, server := range servers {
|
||||
// Add a separator between all servers
|
||||
if i > 0 {
|
||||
table.Append(separator)
|
||||
}
|
||||
// Fill up the service report in alphabetical order
|
||||
services := make([]string, 0, len(stats[server].services))
|
||||
for service := range stats[server].services {
|
||||
services = append(services, service)
|
||||
}
|
||||
sort.Strings(services)
|
||||
|
||||
if len(services) == 0 {
|
||||
table.Append([]string{server, stats[server].address, "", "", ""})
|
||||
}
|
||||
for j, service := range services {
|
||||
// Add an empty line between all services
|
||||
if j > 0 {
|
||||
table.Append([]string{"", "", "", separator[3], separator[4]})
|
||||
}
|
||||
// Fill up the config report in alphabetical order
|
||||
configs := make([]string, 0, len(stats[server].services[service]))
|
||||
for service := range stats[server].services[service] {
|
||||
configs = append(configs, service)
|
||||
}
|
||||
sort.Strings(configs)
|
||||
|
||||
for k, config := range configs {
|
||||
switch {
|
||||
case j == 0 && k == 0:
|
||||
table.Append([]string{server, stats[server].address, service, config, stats[server].services[service][config]})
|
||||
case k == 0:
|
||||
table.Append([]string{"", "", service, config, stats[server].services[service][config]})
|
||||
default:
|
||||
table.Append([]string{"", "", "", config, stats[server].services[service][config]})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
table.Render()
|
||||
}
|
||||
|
||||
// protips contains a collection of network infos to report pro-tips
|
||||
@@ -161,75 +286,3 @@ type protips struct {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ func (w *wizard) manageServers() {
|
||||
w.conf.flush()
|
||||
|
||||
log.Info("Disconnected existing server", "server", server)
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
return
|
||||
}
|
||||
// If the user requested connecting a new server, do it
|
||||
if w.makeServer() != "" {
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,9 +174,10 @@ func (w *wizard) deployComponent() {
|
||||
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")
|
||||
fmt.Println(" 4. Explorer - Chain analysis webservice (ethash only)")
|
||||
fmt.Println(" 5. Wallet - Browser wallet for quick sends")
|
||||
fmt.Println(" 6. Faucet - Crypto faucet to give away funds")
|
||||
fmt.Println(" 7. Dashboard - Website listing above web-services")
|
||||
|
||||
switch w.read() {
|
||||
case "1":
|
||||
@@ -186,9 +187,12 @@ func (w *wizard) deployComponent() {
|
||||
case "3":
|
||||
w.deployNode(false)
|
||||
case "4":
|
||||
w.deployExplorer()
|
||||
case "5":
|
||||
w.deployFaucet()
|
||||
w.deployWallet()
|
||||
case "6":
|
||||
w.deployFaucet()
|
||||
case "7":
|
||||
w.deployDashboard()
|
||||
default:
|
||||
log.Error("That's not something I can do")
|
||||
|
||||
@@ -29,7 +29,8 @@ import (
|
||||
//
|
||||
// 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 {
|
||||
proxy, _ := checkNginx(client, w.network)
|
||||
if proxy != nil {
|
||||
// Reverse proxy is running, if ports match, we need a virtual host
|
||||
if proxy.port == port {
|
||||
fmt.Println()
|
||||
@@ -41,7 +42,13 @@ func (w *wizard) ensureVirtualHost(client *sshClient, port int, def string) (str
|
||||
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 {
|
||||
nocache := false
|
||||
if proxy != nil {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the reverse-proxy be rebuilt from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
if out, err := deployNginx(client, w.network, port, nocache); err != nil {
|
||||
log.Error("Failed to deploy reverse-proxy", "err", err)
|
||||
if len(out) > 0 {
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
// 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 {
|
||||
if w.conf.Genesis == nil {
|
||||
log.Error("No genesis block configured")
|
||||
return
|
||||
}
|
||||
@@ -44,17 +44,19 @@ func (w *wizard) deployNode(boot bool) {
|
||||
}
|
||||
client := w.servers[server]
|
||||
|
||||
// Retrieve any active ethstats configurations from the server
|
||||
// Retrieve any active node configurations from the server
|
||||
infos, err := checkNode(client, w.network, boot)
|
||||
if err != nil {
|
||||
if boot {
|
||||
infos = &nodeInfos{portFull: 30303, peersTotal: 512, peersLight: 256}
|
||||
infos = &nodeInfos{port: 30303, peersTotal: 512, peersLight: 256}
|
||||
} else {
|
||||
infos = &nodeInfos{portFull: 30303, peersTotal: 50, peersLight: 0, gasTarget: 4.7, gasPrice: 18}
|
||||
infos = &nodeInfos{port: 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()
|
||||
existed := err == nil
|
||||
|
||||
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()
|
||||
@@ -65,10 +67,20 @@ func (w *wizard) deployNode(boot bool) {
|
||||
fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.datadir)
|
||||
infos.datadir = w.readDefaultString(infos.datadir)
|
||||
}
|
||||
if w.conf.Genesis.Config.Ethash != nil && !boot {
|
||||
fmt.Println()
|
||||
if infos.ethashdir == "" {
|
||||
fmt.Printf("Where should the ethash mining DAGs be stored on the remote machine?\n")
|
||||
infos.ethashdir = w.readString()
|
||||
} else {
|
||||
fmt.Printf("Where should the ethash mining DAGs be stored on the remote machine? (default = %s)\n", infos.ethashdir)
|
||||
infos.ethashdir = w.readDefaultString(infos.ethashdir)
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
fmt.Printf("Which TCP/UDP port to listen on? (default = %d)\n", infos.port)
|
||||
infos.port = w.readDefaultInt(infos.port)
|
||||
|
||||
// Figure out how many peers to allow (different based on node type)
|
||||
fmt.Println()
|
||||
@@ -91,7 +103,7 @@ func (w *wizard) deployNode(boot bool) {
|
||||
}
|
||||
// If the node is a miner/signer, load up needed credentials
|
||||
if !boot {
|
||||
if w.conf.genesis.Config.Ethash != nil {
|
||||
if w.conf.Genesis.Config.Ethash != nil {
|
||||
// Ethash based miners only need an etherbase to mine against
|
||||
fmt.Println()
|
||||
if infos.etherbase == "" {
|
||||
@@ -106,7 +118,7 @@ func (w *wizard) deployNode(boot bool) {
|
||||
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 {
|
||||
} 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 {
|
||||
@@ -145,7 +157,13 @@ func (w *wizard) deployNode(boot bool) {
|
||||
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 {
|
||||
nocache := false
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the node be built from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
if out, err := deployNode(client, w.network, w.conf.bootnodes, infos, nocache); err != nil {
|
||||
log.Error("Failed to deploy Ethereum node container", "err", err)
|
||||
if len(out) > 0 {
|
||||
fmt.Printf("%s\n", out)
|
||||
@@ -156,5 +174,5 @@ func (w *wizard) deployNode(boot bool) {
|
||||
log.Info("Waiting for node to finish booting")
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
w.networkStats(false)
|
||||
w.networkStats()
|
||||
}
|
||||
|
||||
113
cmd/puppeth/wizard_wallet.go
Normal file
113
cmd/puppeth/wizard_wallet.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// 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/log"
|
||||
)
|
||||
|
||||
// deployWallet creates a new web wallet based on some user input.
|
||||
func (w *wizard) deployWallet() {
|
||||
// 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 node configurations from the server
|
||||
infos, err := checkWallet(client, w.network)
|
||||
if err != nil {
|
||||
infos = &walletInfos{
|
||||
nodePort: 30303, rpcPort: 8545, webPort: 80, webHost: client.server,
|
||||
}
|
||||
}
|
||||
existed := err == nil
|
||||
|
||||
infos.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
|
||||
infos.network = w.conf.Genesis.Config.ChainId.Int64()
|
||||
|
||||
// Figure out which port to listen on
|
||||
fmt.Println()
|
||||
fmt.Printf("Which port should the wallet listen on? (default = %d)\n", infos.webPort)
|
||||
infos.webPort = w.readDefaultInt(infos.webPort)
|
||||
|
||||
// Figure which virtual-host to deploy ethstats on
|
||||
if infos.webHost, err = w.ensureVirtualHost(client, infos.webPort, infos.webHost); err != nil {
|
||||
log.Error("Failed to decide on wallet host", "err", err)
|
||||
return
|
||||
}
|
||||
// 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 should the backing node listen on? (default = %d)\n", infos.nodePort)
|
||||
infos.nodePort = w.readDefaultInt(infos.nodePort)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("Which port should the backing RPC API listen on? (default = %d)\n", infos.rpcPort)
|
||||
infos.rpcPort = w.readDefaultInt(infos.rpcPort)
|
||||
|
||||
// Set a proper name to report on the stats page
|
||||
fmt.Println()
|
||||
if infos.ethstats == "" {
|
||||
fmt.Printf("What should the wallet be called on the stats page?\n")
|
||||
infos.ethstats = w.readString() + ":" + w.conf.ethstats
|
||||
} else {
|
||||
fmt.Printf("What should the wallet be called on the stats page? (default = %s)\n", infos.ethstats)
|
||||
infos.ethstats = w.readDefaultString(infos.ethstats) + ":" + w.conf.ethstats
|
||||
}
|
||||
// Try to deploy the wallet on the host
|
||||
nocache := false
|
||||
if existed {
|
||||
fmt.Println()
|
||||
fmt.Printf("Should the wallet be built from scratch (y/n)? (default = no)\n")
|
||||
nocache = w.readDefaultString("n") != "n"
|
||||
}
|
||||
if out, err := deployWallet(client, w.network, w.conf.bootnodes, infos, nocache); err != nil {
|
||||
log.Error("Failed to deploy wallet 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()
|
||||
}
|
||||
321
cmd/swarm/config.go
Normal file
321
cmd/swarm/config.go
Normal file
@@ -0,0 +1,321 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unicode"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/naoina/toml"
|
||||
|
||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||
)
|
||||
|
||||
var (
|
||||
//flag definition for the dumpconfig command
|
||||
DumpConfigCommand = cli.Command{
|
||||
Action: utils.MigrateFlags(dumpConfig),
|
||||
Name: "dumpconfig",
|
||||
Usage: "Show configuration values",
|
||||
ArgsUsage: "",
|
||||
Flags: app.Flags,
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `The dumpconfig command shows configuration values.`,
|
||||
}
|
||||
|
||||
//flag definition for the config file command
|
||||
SwarmTomlConfigPathFlag = cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "TOML configuration file",
|
||||
}
|
||||
)
|
||||
|
||||
//constants for environment variables
|
||||
const (
|
||||
SWARM_ENV_CHEQUEBOOK_ADDR = "SWARM_CHEQUEBOOK_ADDR"
|
||||
SWARM_ENV_ACCOUNT = "SWARM_ACCOUNT"
|
||||
SWARM_ENV_LISTEN_ADDR = "SWARM_LISTEN_ADDR"
|
||||
SWARM_ENV_PORT = "SWARM_PORT"
|
||||
SWARM_ENV_NETWORK_ID = "SWARM_NETWORK_ID"
|
||||
SWARM_ENV_SWAP_ENABLE = "SWARM_SWAP_ENABLE"
|
||||
SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
|
||||
SWARM_ENV_SYNC_ENABLE = "SWARM_SYNC_ENABLE"
|
||||
SWARM_ENV_ENS_API = "SWARM_ENS_API"
|
||||
SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR"
|
||||
SWARM_ENV_CORS = "SWARM_CORS"
|
||||
SWARM_ENV_BOOTNODES = "SWARM_BOOTNODES"
|
||||
GETH_ENV_DATADIR = "GETH_DATADIR"
|
||||
)
|
||||
|
||||
// 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(", check github.com/ethereum/go-ethereum/swarm/api/config.go for available fields")
|
||||
}
|
||||
return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link)
|
||||
},
|
||||
}
|
||||
|
||||
//before booting the swarm node, build the configuration
|
||||
func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
|
||||
//check for deprecated flags
|
||||
checkDeprecated(ctx)
|
||||
//start by creating a default config
|
||||
config = bzzapi.NewDefaultConfig()
|
||||
//first load settings from config file (if provided)
|
||||
config, err = configFileOverride(config, ctx)
|
||||
//override settings provided by environment variables
|
||||
config = envVarsOverride(config)
|
||||
//override settings provided by command line
|
||||
config = cmdLineOverride(config, ctx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//finally, after the configuration build phase is finished, initialize
|
||||
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context) {
|
||||
//at this point, all vars should be set in the Config
|
||||
//get the account for the provided swarm account
|
||||
prvkey := getAccount(config.BzzAccount, ctx, stack)
|
||||
//set the resolved config path (geth --datadir)
|
||||
config.Path = stack.InstanceDir()
|
||||
//finally, initialize the configuration
|
||||
config.Init(prvkey)
|
||||
//configuration phase completed here
|
||||
log.Debug("Starting Swarm with the following parameters:")
|
||||
//after having created the config, print it to screen
|
||||
log.Debug(printConfig(config))
|
||||
}
|
||||
|
||||
//override the current config with whatever is in the config file, if a config file has been provided
|
||||
func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) {
|
||||
var err error
|
||||
|
||||
//only do something if the -config flag has been set
|
||||
if ctx.GlobalIsSet(SwarmTomlConfigPathFlag.Name) {
|
||||
var filepath string
|
||||
if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" {
|
||||
utils.Fatalf("Config file flag provided with invalid file path")
|
||||
}
|
||||
f, err := os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
//decode the TOML file into a Config struct
|
||||
//note that we are decoding into the existing defaultConfig;
|
||||
//if an entry is not present in the file, the default entry is kept
|
||||
err = tomlSettings.NewDecoder(f).Decode(&config)
|
||||
// Add file name to errors that have a line number.
|
||||
if _, ok := err.(*toml.LineError); ok {
|
||||
err = errors.New(filepath + ", " + err.Error())
|
||||
}
|
||||
}
|
||||
return config, err
|
||||
}
|
||||
|
||||
//override the current config with whatever is provided through the command line
|
||||
//most values are not allowed a zero value (empty string), if not otherwise noted
|
||||
func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
|
||||
|
||||
if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
|
||||
currentConfig.BzzAccount = keyid
|
||||
}
|
||||
|
||||
if chbookaddr := ctx.GlobalString(ChequebookAddrFlag.Name); chbookaddr != "" {
|
||||
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||
}
|
||||
|
||||
if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
|
||||
if id, _ := strconv.Atoi(networkid); id != 0 {
|
||||
currentConfig.NetworkId = uint64(id)
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
||||
if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" {
|
||||
currentConfig.Path = datadir
|
||||
}
|
||||
}
|
||||
|
||||
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
|
||||
if len(bzzport) > 0 {
|
||||
currentConfig.Port = bzzport
|
||||
}
|
||||
|
||||
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
|
||||
currentConfig.ListenAddr = bzzaddr
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmSwapEnabledFlag.Name) {
|
||||
currentConfig.SwapEnabled = true
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmSyncEnabledFlag.Name) {
|
||||
currentConfig.SyncEnabled = true
|
||||
}
|
||||
|
||||
currentConfig.SwapApi = ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
||||
if currentConfig.SwapEnabled && currentConfig.SwapApi == "" {
|
||||
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
|
||||
}
|
||||
|
||||
//EnsApi can be set to "", so can't check for empty string, as it is allowed!
|
||||
if ctx.GlobalIsSet(EnsAPIFlag.Name) {
|
||||
currentConfig.EnsApi = ctx.GlobalString(EnsAPIFlag.Name)
|
||||
}
|
||||
|
||||
if ensaddr := ctx.GlobalString(EnsAddrFlag.Name); ensaddr != "" {
|
||||
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||
}
|
||||
|
||||
if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" {
|
||||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
||||
currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name)
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
|
||||
}
|
||||
|
||||
//override the current config with whatver is provided in environment variables
|
||||
//most values are not allowed a zero value (empty string), if not otherwise noted
|
||||
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||
|
||||
if keyid := os.Getenv(SWARM_ENV_ACCOUNT); keyid != "" {
|
||||
currentConfig.BzzAccount = keyid
|
||||
}
|
||||
|
||||
if chbookaddr := os.Getenv(SWARM_ENV_CHEQUEBOOK_ADDR); chbookaddr != "" {
|
||||
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||
}
|
||||
|
||||
if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" {
|
||||
if id, _ := strconv.Atoi(networkid); id != 0 {
|
||||
currentConfig.NetworkId = uint64(id)
|
||||
}
|
||||
}
|
||||
|
||||
if datadir := os.Getenv(GETH_ENV_DATADIR); datadir != "" {
|
||||
currentConfig.Path = datadir
|
||||
}
|
||||
|
||||
bzzport := os.Getenv(SWARM_ENV_PORT)
|
||||
if len(bzzport) > 0 {
|
||||
currentConfig.Port = bzzport
|
||||
}
|
||||
|
||||
if bzzaddr := os.Getenv(SWARM_ENV_LISTEN_ADDR); bzzaddr != "" {
|
||||
currentConfig.ListenAddr = bzzaddr
|
||||
}
|
||||
|
||||
if swapenable := os.Getenv(SWARM_ENV_SWAP_ENABLE); swapenable != "" {
|
||||
if swap, err := strconv.ParseBool(swapenable); err != nil {
|
||||
currentConfig.SwapEnabled = swap
|
||||
}
|
||||
}
|
||||
|
||||
if syncenable := os.Getenv(SWARM_ENV_SYNC_ENABLE); syncenable != "" {
|
||||
if sync, err := strconv.ParseBool(syncenable); err != nil {
|
||||
currentConfig.SyncEnabled = sync
|
||||
}
|
||||
}
|
||||
|
||||
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
|
||||
currentConfig.SwapApi = swapapi
|
||||
}
|
||||
|
||||
if currentConfig.SwapEnabled && currentConfig.SwapApi == "" {
|
||||
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
|
||||
}
|
||||
|
||||
//EnsApi can be set to "", so can't check for empty string, as it is allowed
|
||||
if ensapi, exists := os.LookupEnv(SWARM_ENV_ENS_API); exists {
|
||||
currentConfig.EnsApi = ensapi
|
||||
}
|
||||
|
||||
if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" {
|
||||
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||
}
|
||||
|
||||
if cors := os.Getenv(SWARM_ENV_CORS); cors != "" {
|
||||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if bootnodes := os.Getenv(SWARM_ENV_BOOTNODES); bootnodes != "" {
|
||||
currentConfig.BootNodes = bootnodes
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
}
|
||||
|
||||
// dumpConfig is the dumpconfig command.
|
||||
// writes a default config to STDOUT
|
||||
func dumpConfig(ctx *cli.Context) error {
|
||||
cfg, err := buildConfig(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf(fmt.Sprintf("Uh oh - dumpconfig triggered an error %v", err))
|
||||
}
|
||||
comment := ""
|
||||
out, err := tomlSettings.Marshal(&cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.WriteString(os.Stdout, comment)
|
||||
os.Stdout.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
//deprecated flags checked here
|
||||
func checkDeprecated(ctx *cli.Context) {
|
||||
// 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.")
|
||||
}
|
||||
}
|
||||
|
||||
//print a Config as string
|
||||
func printConfig(config *bzzapi.Config) string {
|
||||
out, err := tomlSettings.Marshal(&config)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Something is not right with the configuration: %v", err)
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
459
cmd/swarm/config_test.go
Normal file
459
cmd/swarm/config_test.go
Normal file
@@ -0,0 +1,459 @@
|
||||
// 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"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
)
|
||||
|
||||
func TestDumpConfig(t *testing.T) {
|
||||
swarm := runSwarm(t, "dumpconfig")
|
||||
defaultConf := api.NewDefaultConfig()
|
||||
out, err := tomlSettings.Marshal(&defaultConf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
swarm.Expect(string(out))
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestFailsSwapEnabledNoSwapApi(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
|
||||
}
|
||||
|
||||
swarm := runSwarm(t, flags...)
|
||||
swarm.Expect("Fatal: " + SWARM_ERR_SWAP_SET_NO_API + "\n")
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestFailsNoBzzAccount(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||
}
|
||||
|
||||
swarm := runSwarm(t, flags...)
|
||||
swarm.Expect("Fatal: " + SWARM_ERR_NO_BZZACCOUNT + "\n")
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestCmdLineOverrides(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||
fmt.Sprintf("--%s", SwarmSyncEnabledFlag.Name),
|
||||
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
|
||||
"--datadir", dir,
|
||||
"--ipcpath", conf.IPCPath,
|
||||
}
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
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)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkId != 42 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkId)
|
||||
}
|
||||
|
||||
if !info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be enabled, but is false")
|
||||
}
|
||||
|
||||
if info.Cors != "*" {
|
||||
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
|
||||
}
|
||||
|
||||
node.Shutdown()
|
||||
}
|
||||
|
||||
func TestFileOverrides(t *testing.T) {
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//create a config file
|
||||
//first, create a default conf
|
||||
defaultConf := api.NewDefaultConfig()
|
||||
//change some values in order to test if they have been loaded
|
||||
defaultConf.SyncEnabled = true
|
||||
defaultConf.NetworkId = 54
|
||||
defaultConf.Port = httpPort
|
||||
defaultConf.StoreParams.DbCapacity = 9000000
|
||||
defaultConf.ChunkerParams.Branches = 64
|
||||
defaultConf.HiveParams.CallInterval = 6000000000
|
||||
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||
defaultConf.SyncParams.KeyBufferSize = 512
|
||||
//create a TOML string
|
||||
out, err := tomlSettings.Marshal(&defaultConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//create file
|
||||
f, err := ioutil.TempFile("", "testconfig.toml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//write file
|
||||
_, err = f.WriteString(string(out))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
f.Sync()
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
"--ens-api", "",
|
||||
"--ipcpath", conf.IPCPath,
|
||||
"--datadir", dir,
|
||||
}
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
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)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkId != 54 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||
}
|
||||
|
||||
if !info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be enabled, but is false")
|
||||
}
|
||||
|
||||
if info.StoreParams.DbCapacity != 9000000 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||
}
|
||||
|
||||
if info.ChunkerParams.Branches != 64 {
|
||||
t.Fatalf("Expected chunker params branches to be %d, got %d", 64, info.ChunkerParams.Branches)
|
||||
}
|
||||
|
||||
if info.HiveParams.CallInterval != 6000000000 {
|
||||
t.Fatalf("Expected HiveParams CallInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.CallInterval))
|
||||
}
|
||||
|
||||
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
|
||||
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
|
||||
}
|
||||
|
||||
if info.SyncParams.KeyBufferSize != 512 {
|
||||
t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
|
||||
}
|
||||
|
||||
node.Shutdown()
|
||||
}
|
||||
|
||||
func TestEnvVars(t *testing.T) {
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
envVars := os.Environ()
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999"))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*"))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncEnabledFlag.EnvVar, "true"))
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
"--ens-api", "",
|
||||
"--datadir", dir,
|
||||
"--ipcpath", conf.IPCPath,
|
||||
}
|
||||
|
||||
//node.Cmd = runSwarm(t,flags...)
|
||||
//node.Cmd.cmd.Env = envVars
|
||||
//the above assignment does not work, so we need a custom Cmd here in order to pass envVars:
|
||||
cmd := &exec.Cmd{
|
||||
Path: reexec.Self(),
|
||||
Args: append([]string{"swarm-test"}, flags...),
|
||||
Stderr: os.Stderr,
|
||||
Stdout: os.Stdout,
|
||||
}
|
||||
cmd.Env = envVars
|
||||
//stdout, err := cmd.StdoutPipe()
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//stdout = bufio.NewReader(stdout)
|
||||
var stdin io.WriteCloser
|
||||
if stdin, err = cmd.StdinPipe(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//cmd.InputLine(testPassphrase)
|
||||
io.WriteString(stdin, testPassphrase+"\n")
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
// 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)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkId != 999 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkId)
|
||||
}
|
||||
|
||||
if info.Cors != "*" {
|
||||
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
|
||||
}
|
||||
|
||||
if !info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be enabled, but is false")
|
||||
}
|
||||
|
||||
node.Shutdown()
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func TestCmdLineOverridesFile(t *testing.T) {
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//create a config file
|
||||
//first, create a default conf
|
||||
defaultConf := api.NewDefaultConfig()
|
||||
//change some values in order to test if they have been loaded
|
||||
defaultConf.SyncEnabled = false
|
||||
defaultConf.NetworkId = 54
|
||||
defaultConf.Port = "8588"
|
||||
defaultConf.StoreParams.DbCapacity = 9000000
|
||||
defaultConf.ChunkerParams.Branches = 64
|
||||
defaultConf.HiveParams.CallInterval = 6000000000
|
||||
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||
defaultConf.SyncParams.KeyBufferSize = 512
|
||||
//create a TOML file
|
||||
out, err := tomlSettings.Marshal(&defaultConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//write file
|
||||
f, err := ioutil.TempFile("", "testconfig.toml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//write file
|
||||
_, err = f.WriteString(string(out))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
f.Sync()
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
expectNetworkId := uint64(77)
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||
fmt.Sprintf("--%s", SwarmSyncEnabledFlag.Name),
|
||||
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
"--ens-api", "",
|
||||
"--datadir", dir,
|
||||
"--ipcpath", conf.IPCPath,
|
||||
}
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
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)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkId != expectNetworkId {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkId)
|
||||
}
|
||||
|
||||
if !info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be enabled, but is false")
|
||||
}
|
||||
|
||||
if info.StoreParams.DbCapacity != 9000000 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||
}
|
||||
|
||||
if info.ChunkerParams.Branches != 64 {
|
||||
t.Fatalf("Expected chunker params branches to be %d, got %d", 64, info.ChunkerParams.Branches)
|
||||
}
|
||||
|
||||
if info.HiveParams.CallInterval != 6000000000 {
|
||||
t.Fatalf("Expected HiveParams CallInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.CallInterval))
|
||||
}
|
||||
|
||||
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
|
||||
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
|
||||
}
|
||||
|
||||
if info.SyncParams.KeyBufferSize != 512 {
|
||||
t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
|
||||
}
|
||||
|
||||
node.Shutdown()
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
@@ -66,49 +67,58 @@ var (
|
||||
|
||||
var (
|
||||
ChequebookAddrFlag = cli.StringFlag{
|
||||
Name: "chequebook",
|
||||
Usage: "chequebook contract address",
|
||||
Name: "chequebook",
|
||||
Usage: "chequebook contract address",
|
||||
EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
|
||||
}
|
||||
SwarmAccountFlag = cli.StringFlag{
|
||||
Name: "bzzaccount",
|
||||
Usage: "Swarm account key file",
|
||||
Name: "bzzaccount",
|
||||
Usage: "Swarm account key file",
|
||||
EnvVar: SWARM_ENV_ACCOUNT,
|
||||
}
|
||||
SwarmListenAddrFlag = cli.StringFlag{
|
||||
Name: "httpaddr",
|
||||
Usage: "Swarm HTTP API listening interface",
|
||||
Name: "httpaddr",
|
||||
Usage: "Swarm HTTP API listening interface",
|
||||
EnvVar: SWARM_ENV_LISTEN_ADDR,
|
||||
}
|
||||
SwarmPortFlag = cli.StringFlag{
|
||||
Name: "bzzport",
|
||||
Usage: "Swarm local http api port",
|
||||
Name: "bzzport",
|
||||
Usage: "Swarm local http api port",
|
||||
EnvVar: SWARM_ENV_PORT,
|
||||
}
|
||||
SwarmNetworkIdFlag = cli.IntFlag{
|
||||
Name: "bzznetworkid",
|
||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||
Name: "bzznetworkid",
|
||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||
EnvVar: SWARM_ENV_NETWORK_ID,
|
||||
}
|
||||
SwarmConfigPathFlag = cli.StringFlag{
|
||||
Name: "bzzconfig",
|
||||
Usage: "Swarm config file path (datadir/bzz)",
|
||||
Usage: "DEPRECATED: please use --config path/to/TOML-file",
|
||||
}
|
||||
SwarmSwapEnabledFlag = cli.BoolFlag{
|
||||
Name: "swap",
|
||||
Usage: "Swarm SWAP enabled (default false)",
|
||||
Name: "swap",
|
||||
Usage: "Swarm SWAP enabled (default false)",
|
||||
EnvVar: SWARM_ENV_SWAP_ENABLE,
|
||||
}
|
||||
SwarmSwapAPIFlag = cli.StringFlag{
|
||||
Name: "swap-api",
|
||||
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||
Name: "swap-api",
|
||||
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||
EnvVar: SWARM_ENV_SWAP_API,
|
||||
}
|
||||
SwarmSyncEnabledFlag = cli.BoolTFlag{
|
||||
Name: "sync",
|
||||
Usage: "Swarm Syncing enabled (default true)",
|
||||
Name: "sync",
|
||||
Usage: "Swarm Syncing enabled (default true)",
|
||||
EnvVar: SWARM_ENV_SYNC_ENABLE,
|
||||
}
|
||||
EnsAPIFlag = cli.StringFlag{
|
||||
Name: "ens-api",
|
||||
Usage: "URL of the Ethereum API provider to use for ENS record lookups",
|
||||
Value: node.DefaultIPCEndpoint("geth"),
|
||||
Name: "ens-api",
|
||||
Usage: "URL of the Ethereum API provider to use for ENS record lookups",
|
||||
EnvVar: SWARM_ENV_ENS_API,
|
||||
}
|
||||
EnsAddrFlag = cli.StringFlag{
|
||||
Name: "ens-addr",
|
||||
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
|
||||
Name: "ens-addr",
|
||||
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
|
||||
EnvVar: SWARM_ENV_ENS_ADDR,
|
||||
}
|
||||
SwarmApiFlag = cli.StringFlag{
|
||||
Name: "bzzapi",
|
||||
@@ -136,8 +146,9 @@ var (
|
||||
Usage: "force mime type",
|
||||
}
|
||||
CorsStringFlag = cli.StringFlag{
|
||||
Name: "corsdomain",
|
||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||
Name: "corsdomain",
|
||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||
EnvVar: SWARM_ENV_CORS,
|
||||
}
|
||||
|
||||
// the following flags are deprecated and should be removed in the future
|
||||
@@ -147,6 +158,12 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
//declare a few constant error messages, useful for later error check comparisons in test
|
||||
var (
|
||||
SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
||||
SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
|
||||
)
|
||||
|
||||
var defaultNodeConfig = node.DefaultConfig
|
||||
|
||||
// This init function sets defaults so cmd/swarm can run alongside geth.
|
||||
@@ -302,6 +319,8 @@ Remove corrupt entries from a local chunk database.
|
||||
DEPRECATED: use 'swarm db clean'.
|
||||
`,
|
||||
},
|
||||
// See config.go
|
||||
DumpConfigCommand,
|
||||
}
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
@@ -325,6 +344,7 @@ DEPRECATED: use 'swarm db clean'.
|
||||
CorsStringFlag,
|
||||
EnsAPIFlag,
|
||||
EnsAddrFlag,
|
||||
SwarmTomlConfigPathFlag,
|
||||
SwarmConfigPathFlag,
|
||||
SwarmSwapEnabledFlag,
|
||||
SwarmSwapAPIFlag,
|
||||
@@ -377,19 +397,32 @@ func version(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
func bzzd(ctx *cli.Context) error {
|
||||
// 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.")
|
||||
//build a valid bzzapi.Config from all available sources:
|
||||
//default config, file config, command line and env vars
|
||||
bzzconfig, err := buildConfig(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to configure swarm: %v", err)
|
||||
}
|
||||
|
||||
cfg := defaultNodeConfig
|
||||
//geth only supports --datadir via command line
|
||||
//in order to be consistent within swarm, if we pass --datadir via environment variable
|
||||
//or via config file, we get the same directory for geth and swarm
|
||||
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
||||
cfg.DataDir = bzzconfig.Path
|
||||
}
|
||||
//setup the ethereum node
|
||||
utils.SetNodeConfig(ctx, &cfg)
|
||||
stack, err := node.New(&cfg)
|
||||
if err != nil {
|
||||
utils.Fatalf("can't create node: %v", err)
|
||||
}
|
||||
|
||||
registerBzzService(ctx, stack)
|
||||
//a few steps need to be done after the config phase is completed,
|
||||
//due to overriding behavior
|
||||
initSwarmNode(bzzconfig, stack, ctx)
|
||||
//register BZZ as node.Service in the ethereum node
|
||||
registerBzzService(bzzconfig, ctx, stack)
|
||||
//start the node
|
||||
utils.StartNode(stack)
|
||||
|
||||
go func() {
|
||||
@@ -401,13 +434,12 @@ func bzzd(ctx *cli.Context) error {
|
||||
stack.Stop()
|
||||
}()
|
||||
|
||||
networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
|
||||
// Add bootnodes as initial peers.
|
||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
||||
bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
|
||||
if bzzconfig.BootNodes != "" {
|
||||
bootnodes := strings.Split(bzzconfig.BootNodes, ",")
|
||||
injectBootnodes(stack.Server(), bootnodes)
|
||||
} else {
|
||||
if networkId == 3 {
|
||||
if bzzconfig.NetworkId == 3 {
|
||||
injectBootnodes(stack.Server(), testbetBootNodes)
|
||||
}
|
||||
}
|
||||
@@ -448,61 +480,31 @@ func detectEnsAddr(client *rpc.Client) (common.Address, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
||||
prvkey := getAccount(ctx, stack)
|
||||
|
||||
chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
|
||||
bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name)
|
||||
if bzzdir == "" {
|
||||
bzzdir = stack.InstanceDir()
|
||||
}
|
||||
|
||||
bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name))
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to configure swarm: %v", err)
|
||||
}
|
||||
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
|
||||
if len(bzzport) > 0 {
|
||||
bzzconfig.Port = bzzport
|
||||
}
|
||||
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
|
||||
bzzconfig.ListenAddr = bzzaddr
|
||||
}
|
||||
swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
|
||||
syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.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)
|
||||
func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
|
||||
|
||||
//define the swarm service boot function
|
||||
boot := func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
var swapClient *ethclient.Client
|
||||
if swapapi != "" {
|
||||
log.Info("connecting to SWAP API", "url", swapapi)
|
||||
swapClient, err = ethclient.Dial(swapapi)
|
||||
var err error
|
||||
if bzzconfig.SwapApi != "" {
|
||||
log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi)
|
||||
swapClient, err = ethclient.Dial(bzzconfig.SwapApi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", swapapi, err)
|
||||
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err)
|
||||
}
|
||||
}
|
||||
|
||||
var ensClient *ethclient.Client
|
||||
if ensapi != "" {
|
||||
log.Info("connecting to ENS API", "url", ensapi)
|
||||
client, err := rpc.Dial(ensapi)
|
||||
if bzzconfig.EnsApi != "" {
|
||||
log.Info("connecting to ENS API", "url", bzzconfig.EnsApi)
|
||||
client, err := rpc.Dial(bzzconfig.EnsApi)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error connecting to ENS API %s: %s", ensapi, err)
|
||||
return nil, fmt.Errorf("error connecting to ENS API %s: %s", bzzconfig.EnsApi, err)
|
||||
}
|
||||
ensClient = ethclient.NewClient(client)
|
||||
|
||||
if ensAddr != "" {
|
||||
bzzconfig.EnsRoot = common.HexToAddress(ensAddr)
|
||||
} else {
|
||||
//no ENS root address set yet
|
||||
if bzzconfig.EnsRoot == (common.Address{}) {
|
||||
ensAddr, err := detectEnsAddr(client)
|
||||
if err == nil {
|
||||
bzzconfig.EnsRoot = ensAddr
|
||||
@@ -512,21 +514,21 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, swapEnabled, syncEnabled, cors)
|
||||
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, bzzconfig.SwapEnabled, bzzconfig.SyncEnabled, bzzconfig.Cors)
|
||||
}
|
||||
//register within the ethereum node
|
||||
if err := stack.Register(boot); err != nil {
|
||||
utils.Fatalf("Failed to register the Swarm service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||
keyid := ctx.GlobalString(SwarmAccountFlag.Name)
|
||||
|
||||
if keyid == "" {
|
||||
utils.Fatalf("Option %q is required", SwarmAccountFlag.Name)
|
||||
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||
//an account is mandatory
|
||||
if bzzaccount == "" {
|
||||
utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
|
||||
}
|
||||
// Try to load the arg as a hex key file.
|
||||
if key, err := crypto.LoadECDSA(keyid); err == nil {
|
||||
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
|
||||
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
||||
return key
|
||||
}
|
||||
@@ -534,7 +536,7 @@ func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||
am := stack.AccountManager()
|
||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
|
||||
return decryptStoreAccount(ks, keyid, utils.MakePasswordList(ctx))
|
||||
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
|
||||
}
|
||||
|
||||
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
|
||||
@@ -552,7 +554,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
|
||||
utils.Fatalf("Can't find swarm account key %s", account)
|
||||
}
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't find swarm account key: %v", err)
|
||||
utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
|
||||
}
|
||||
keyjson, err := ioutil.ReadFile(a.URL.Path)
|
||||
if err != nil {
|
||||
|
||||
@@ -30,6 +30,8 @@ import (
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
const bzzManifestJSON = "application/bzz-manifest+json"
|
||||
|
||||
func add(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) < 3 {
|
||||
@@ -145,7 +147,7 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
|
||||
if path == entry.Path {
|
||||
utils.Fatalf("Path %s already present, not adding anything", path)
|
||||
} else {
|
||||
if entry.ContentType == "application/bzz-manifest+json" {
|
||||
if entry.ContentType == bzzManifestJSON {
|
||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||
longestPathEntry = entry
|
||||
@@ -207,7 +209,7 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
|
||||
if path == entry.Path {
|
||||
newEntry = entry
|
||||
} else {
|
||||
if entry.ContentType == "application/bzz-manifest+json" {
|
||||
if entry.ContentType == bzzManifestJSON {
|
||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||
longestPathEntry = entry
|
||||
@@ -281,7 +283,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
|
||||
if path == entry.Path {
|
||||
entryToRemove = entry
|
||||
} else {
|
||||
if entry.ContentType == "application/bzz-manifest+json" {
|
||||
if entry.ContentType == bzzManifestJSON {
|
||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||
longestPathEntry = entry
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// 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
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
@@ -156,9 +157,9 @@ type testNode struct {
|
||||
|
||||
const testPassphrase = "swarm-test-passphrase"
|
||||
|
||||
func newTestNode(t *testing.T, dir string) *testNode {
|
||||
func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
|
||||
// create key
|
||||
conf := &node.Config{
|
||||
conf = &node.Config{
|
||||
DataDir: dir,
|
||||
IPCPath: "bzzd.ipc",
|
||||
NoUSB: true,
|
||||
@@ -167,18 +168,24 @@ func newTestNode(t *testing.T, dir string) *testNode {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
account, err := n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
|
||||
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())
|
||||
}
|
||||
|
||||
return conf, account
|
||||
}
|
||||
|
||||
func newTestNode(t *testing.T, dir string) *testNode {
|
||||
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// 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
|
||||
|
||||
@@ -116,7 +116,6 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
stream := rlp.NewStream(reader, 0)
|
||||
|
||||
// Run actual the import.
|
||||
@@ -150,25 +149,34 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||
if checkInterrupt() {
|
||||
return fmt.Errorf("interrupted")
|
||||
}
|
||||
if hasAllBlocks(chain, blocks[:i]) {
|
||||
missing := missingBlocks(chain, blocks[:i])
|
||||
if len(missing) == 0 {
|
||||
log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash())
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := chain.InsertChain(blocks[:i]); err != nil {
|
||||
if _, err := chain.InsertChain(missing); err != nil {
|
||||
return fmt.Errorf("invalid block %d: %v", n, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool {
|
||||
for _, b := range bs {
|
||||
if !chain.HasBlock(b.Hash(), b.NumberU64()) {
|
||||
return false
|
||||
func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block {
|
||||
head := chain.CurrentBlock()
|
||||
for i, block := range blocks {
|
||||
// If we're behind the chain head, only check block, state is available at head
|
||||
if head.NumberU64() > block.NumberU64() {
|
||||
if !chain.HasBlock(block.Hash(), block.NumberU64()) {
|
||||
return blocks[i:]
|
||||
}
|
||||
continue
|
||||
}
|
||||
// If we're above the chain head, state availability is a must
|
||||
if !chain.HasBlockAndState(block.Hash(), block.NumberU64()) {
|
||||
return blocks[i:]
|
||||
}
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExportChain(blockchain *core.BlockChain, fn string) error {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// 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 utils
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
|
||||
// per this process can be retrieved.
|
||||
func TestFileDescriptorLimits(t *testing.T) {
|
||||
target := 4096
|
||||
|
||||
if limit, err := getFdLimit(); err != nil || limit <= 0 {
|
||||
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
|
||||
}
|
||||
if err := raiseFdLimit(uint64(target)); err != nil {
|
||||
t.Fatalf("failed to raise file allowance")
|
||||
}
|
||||
if limit, err := getFdLimit(); err != nil || limit < target {
|
||||
t.Fatalf("failed to retrieve raised descriptor limit (have %v, want %v): %v", limit, target, err)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// 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/>.
|
||||
|
||||
// +build linux darwin netbsd openbsd solaris
|
||||
|
||||
package utils
|
||||
|
||||
import "syscall"
|
||||
|
||||
// raiseFdLimit tries to maximize the file descriptor allowance of this process
|
||||
// to the maximum hard-limit allowed by the OS.
|
||||
func raiseFdLimit(max uint64) error {
|
||||
// Get the current limit
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return err
|
||||
}
|
||||
// Try to update the limit to the max allowance
|
||||
limit.Cur = limit.Max
|
||||
if limit.Cur > max {
|
||||
limit.Cur = max
|
||||
}
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFdLimit retrieves the number of file descriptors allowed to be opened by this
|
||||
// process.
|
||||
func getFdLimit() (int, error) {
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(limit.Cur), nil
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// 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 utils
|
||||
|
||||
import "errors"
|
||||
|
||||
// raiseFdLimit tries to maximize the file descriptor allowance of this process
|
||||
// to the maximum hard-limit allowed by the OS.
|
||||
func raiseFdLimit(max uint64) error {
|
||||
// This method is NOP by design:
|
||||
// * Linux/Darwin counterparts need to manually increase per process limits
|
||||
// * On Windows Go uses the CreateFile API, which is limited to 16K files, non
|
||||
// changeable from within a running process
|
||||
// This way we can always "request" raising the limits, which will either have
|
||||
// or not have effect based on the platform we're running on.
|
||||
if max > 16384 {
|
||||
return errors.New("file descriptor limit (16384) reached")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFdLimit retrieves the number of file descriptors allowed to be opened by this
|
||||
// process.
|
||||
func getFdLimit() (int, error) {
|
||||
// Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
|
||||
return 16384, nil
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/clique"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
@@ -96,7 +97,7 @@ func NewApp(gitCommit, usage string) *cli.App {
|
||||
//app.Authors = nil
|
||||
app.Email = ""
|
||||
app.Version = params.Version
|
||||
if gitCommit != "" {
|
||||
if len(gitCommit) >= 8 {
|
||||
app.Version += "-" + gitCommit[:8]
|
||||
}
|
||||
app.Usage = usage
|
||||
@@ -169,7 +170,11 @@ var (
|
||||
Usage: `Blockchain sync mode ("fast", "full", or "light")`,
|
||||
Value: &defaultSyncMode,
|
||||
}
|
||||
|
||||
GCModeFlag = cli.StringFlag{
|
||||
Name: "gcmode",
|
||||
Usage: `Blockchain garbage collection mode ("full", "archive")`,
|
||||
Value: "full",
|
||||
}
|
||||
LightServFlag = cli.IntFlag{
|
||||
Name: "lightserv",
|
||||
Usage: "Maximum percentage of time allowed for serving LES requests (0-90)",
|
||||
@@ -178,7 +183,7 @@ var (
|
||||
LightPeersFlag = cli.IntFlag{
|
||||
Name: "lightpeers",
|
||||
Usage: "Maximum number of LES client peers",
|
||||
Value: 20,
|
||||
Value: eth.DefaultConfig.LightPeers,
|
||||
}
|
||||
LightKDFFlag = cli.BoolFlag{
|
||||
Name: "lightkdf",
|
||||
@@ -217,27 +222,27 @@ var (
|
||||
EthashCachesInMemoryFlag = cli.IntFlag{
|
||||
Name: "ethash.cachesinmem",
|
||||
Usage: "Number of recent ethash caches to keep in memory (16MB each)",
|
||||
Value: eth.DefaultConfig.EthashCachesInMem,
|
||||
Value: eth.DefaultConfig.Ethash.CachesInMem,
|
||||
}
|
||||
EthashCachesOnDiskFlag = cli.IntFlag{
|
||||
Name: "ethash.cachesondisk",
|
||||
Usage: "Number of recent ethash caches to keep on disk (16MB each)",
|
||||
Value: eth.DefaultConfig.EthashCachesOnDisk,
|
||||
Value: eth.DefaultConfig.Ethash.CachesOnDisk,
|
||||
}
|
||||
EthashDatasetDirFlag = DirectoryFlag{
|
||||
Name: "ethash.dagdir",
|
||||
Usage: "Directory to store the ethash mining DAGs (default = inside home folder)",
|
||||
Value: DirectoryString{eth.DefaultConfig.EthashDatasetDir},
|
||||
Value: DirectoryString{eth.DefaultConfig.Ethash.DatasetDir},
|
||||
}
|
||||
EthashDatasetsInMemoryFlag = cli.IntFlag{
|
||||
Name: "ethash.dagsinmem",
|
||||
Usage: "Number of recent ethash mining DAGs to keep in memory (1+GB each)",
|
||||
Value: eth.DefaultConfig.EthashDatasetsInMem,
|
||||
Value: eth.DefaultConfig.Ethash.DatasetsInMem,
|
||||
}
|
||||
EthashDatasetsOnDiskFlag = cli.IntFlag{
|
||||
Name: "ethash.dagsondisk",
|
||||
Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)",
|
||||
Value: eth.DefaultConfig.EthashDatasetsOnDisk,
|
||||
Value: eth.DefaultConfig.Ethash.DatasetsOnDisk,
|
||||
}
|
||||
// Transaction pool settings
|
||||
TxPoolNoLocalsFlag = cli.BoolFlag{
|
||||
@@ -292,8 +297,18 @@ var (
|
||||
// Performance tuning settings
|
||||
CacheFlag = cli.IntFlag{
|
||||
Name: "cache",
|
||||
Usage: "Megabytes of memory allocated to internal caching (min 16MB / database forced)",
|
||||
Value: 128,
|
||||
Usage: "Megabytes of memory allocated to internal caching",
|
||||
Value: 1024,
|
||||
}
|
||||
CacheDatabaseFlag = cli.IntFlag{
|
||||
Name: "cache.database",
|
||||
Usage: "Percentage of cache memory allowance to use for database io",
|
||||
Value: 75,
|
||||
}
|
||||
CacheGCFlag = cli.IntFlag{
|
||||
Name: "cache.gc",
|
||||
Usage: "Percentage of cache memory allowance to use for trie pruning",
|
||||
Value: 25,
|
||||
}
|
||||
TrieCacheGenFlag = cli.IntFlag{
|
||||
Name: "trie-cache-gens",
|
||||
@@ -313,7 +328,7 @@ var (
|
||||
TargetGasLimitFlag = cli.Uint64Flag{
|
||||
Name: "targetgaslimit",
|
||||
Usage: "Target gas limit sets the artificial target gas floor for the blocks to mine",
|
||||
Value: params.GenesisGasLimit.Uint64(),
|
||||
Value: params.GenesisGasLimit,
|
||||
}
|
||||
EtherbaseFlag = cli.StringFlag{
|
||||
Name: "etherbase",
|
||||
@@ -382,6 +397,11 @@ var (
|
||||
Usage: "Comma separated list of domains from which to accept cross origin requests (browser enforced)",
|
||||
Value: "",
|
||||
}
|
||||
RPCVirtualHostsFlag = cli.StringFlag{
|
||||
Name: "rpcvhosts",
|
||||
Usage: "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard.",
|
||||
Value: "localhost",
|
||||
}
|
||||
RPCApiFlag = cli.StringFlag{
|
||||
Name: "rpcapi",
|
||||
Usage: "API's offered over the HTTP-RPC interface",
|
||||
@@ -584,6 +604,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
|
||||
urls = params.TestnetBootnodes
|
||||
case ctx.GlobalBool(RinkebyFlag.Name):
|
||||
urls = params.RinkebyBootnodes
|
||||
case cfg.BootstrapNodes != nil:
|
||||
return // already set, don't apply defaults.
|
||||
}
|
||||
|
||||
cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls))
|
||||
@@ -609,7 +631,7 @@ func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) {
|
||||
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
|
||||
}
|
||||
case ctx.GlobalBool(RinkebyFlag.Name):
|
||||
urls = params.RinkebyV5Bootnodes
|
||||
urls = params.RinkebyBootnodes
|
||||
case cfg.BootstrapNodesV5 != nil:
|
||||
return // already set, don't apply defaults.
|
||||
}
|
||||
@@ -633,14 +655,6 @@ func setListenAddress(ctx *cli.Context, cfg *p2p.Config) {
|
||||
}
|
||||
}
|
||||
|
||||
// setDiscoveryV5Address creates a UDP listening address string from set command
|
||||
// line flags for the V5 discovery protocol.
|
||||
func setDiscoveryV5Address(ctx *cli.Context, cfg *p2p.Config) {
|
||||
if ctx.GlobalIsSet(ListenPortFlag.Name) {
|
||||
cfg.DiscoveryV5Addr = fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name)+1)
|
||||
}
|
||||
}
|
||||
|
||||
// setNAT creates a port mapper from command line flags.
|
||||
func setNAT(ctx *cli.Context, cfg *p2p.Config) {
|
||||
if ctx.GlobalIsSet(NATFlag.Name) {
|
||||
@@ -681,6 +695,8 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.GlobalIsSet(RPCApiFlag.Name) {
|
||||
cfg.HTTPModules = splitAndTrim(ctx.GlobalString(RPCApiFlag.Name))
|
||||
}
|
||||
|
||||
cfg.HTTPVirtualHosts = splitAndTrim(ctx.GlobalString(RPCVirtualHostsFlag.Name))
|
||||
}
|
||||
|
||||
// setWS creates the WebSocket RPC listener interface string from the set
|
||||
@@ -719,13 +735,15 @@ func setIPC(ctx *cli.Context, cfg *node.Config) {
|
||||
// makeDatabaseHandles raises out the number of allowed file handles per process
|
||||
// for Geth and returns half of the allowance to assign to the database.
|
||||
func makeDatabaseHandles() int {
|
||||
if err := raiseFdLimit(2048); err != nil {
|
||||
Fatalf("Failed to raise file descriptor allowance: %v", err)
|
||||
}
|
||||
limit, err := getFdLimit()
|
||||
limit, err := fdlimit.Current()
|
||||
if err != nil {
|
||||
Fatalf("Failed to retrieve file descriptor allowance: %v", err)
|
||||
}
|
||||
if limit < 2048 {
|
||||
if err := fdlimit.Raise(2048); err != nil {
|
||||
Fatalf("Failed to raise file descriptor allowance: %v", err)
|
||||
}
|
||||
}
|
||||
if limit > 2048 { // cap database file descriptors even if more is available
|
||||
limit = 2048
|
||||
}
|
||||
@@ -744,6 +762,12 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
|
||||
if err != nil || index < 0 {
|
||||
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
|
||||
}
|
||||
log.Warn("-------------------------------------------------------------------")
|
||||
log.Warn("Referring to accounts by order in the keystore folder is dangerous!")
|
||||
log.Warn("This functionality is deprecated and will be removed in the future!")
|
||||
log.Warn("Please use explicit addresses! (can search via `geth account list`)")
|
||||
log.Warn("-------------------------------------------------------------------")
|
||||
|
||||
accs := ks.Accounts()
|
||||
if len(accs) <= index {
|
||||
return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs))
|
||||
@@ -760,15 +784,6 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
||||
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
|
||||
}
|
||||
cfg.Etherbase = account.Address
|
||||
return
|
||||
}
|
||||
accounts := ks.Accounts()
|
||||
if (cfg.Etherbase == common.Address{}) {
|
||||
if len(accounts) > 0 {
|
||||
cfg.Etherbase = accounts[0].Address
|
||||
} else {
|
||||
log.Warn("No etherbase set and no accounts found as default")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,24 +809,43 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||
setNodeKey(ctx, cfg)
|
||||
setNAT(ctx, cfg)
|
||||
setListenAddress(ctx, cfg)
|
||||
setDiscoveryV5Address(ctx, cfg)
|
||||
setBootstrapNodes(ctx, cfg)
|
||||
setBootstrapNodesV5(ctx, cfg)
|
||||
|
||||
lightClient := ctx.GlobalBool(LightModeFlag.Name) || ctx.GlobalString(SyncModeFlag.Name) == "light"
|
||||
lightServer := ctx.GlobalInt(LightServFlag.Name) != 0
|
||||
lightPeers := ctx.GlobalInt(LightPeersFlag.Name)
|
||||
|
||||
if ctx.GlobalIsSet(MaxPeersFlag.Name) {
|
||||
cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name)
|
||||
} else {
|
||||
if lightServer {
|
||||
cfg.MaxPeers += lightPeers
|
||||
}
|
||||
if lightClient && ctx.GlobalIsSet(LightPeersFlag.Name) && cfg.MaxPeers < lightPeers {
|
||||
cfg.MaxPeers = lightPeers
|
||||
}
|
||||
}
|
||||
if !(lightClient || lightServer) {
|
||||
lightPeers = 0
|
||||
}
|
||||
ethPeers := cfg.MaxPeers - lightPeers
|
||||
if lightClient {
|
||||
ethPeers = 0
|
||||
}
|
||||
log.Info("Maximum peer count", "ETH", ethPeers, "LES", lightPeers, "total", cfg.MaxPeers)
|
||||
|
||||
if ctx.GlobalIsSet(MaxPendingPeersFlag.Name) {
|
||||
cfg.MaxPendingPeers = ctx.GlobalInt(MaxPendingPeersFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(NoDiscoverFlag.Name) || ctx.GlobalBool(LightModeFlag.Name) {
|
||||
if ctx.GlobalIsSet(NoDiscoverFlag.Name) || lightClient {
|
||||
cfg.NoDiscovery = true
|
||||
}
|
||||
|
||||
// if we're running a light client or server, force enable the v5 peer discovery
|
||||
// unless it is explicitly disabled with --nodiscover note that explicitly specifying
|
||||
// --v5disc overrides --nodiscover, in which case the later only disables v4 discovery
|
||||
forceV5Discovery := (ctx.GlobalBool(LightModeFlag.Name) || ctx.GlobalInt(LightServFlag.Name) > 0) && !ctx.GlobalBool(NoDiscoverFlag.Name)
|
||||
forceV5Discovery := (lightClient || lightServer) && !ctx.GlobalBool(NoDiscoverFlag.Name)
|
||||
if ctx.GlobalIsSet(DiscoveryV5Flag.Name) {
|
||||
cfg.DiscoveryV5 = ctx.GlobalBool(DiscoveryV5Flag.Name)
|
||||
} else if forceV5Discovery {
|
||||
@@ -830,7 +864,6 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||
// --dev mode can't use p2p networking.
|
||||
cfg.MaxPeers = 0
|
||||
cfg.ListenAddr = ":0"
|
||||
cfg.DiscoveryV5Addr = ":0"
|
||||
cfg.NoDiscovery = true
|
||||
cfg.DiscoveryV5 = false
|
||||
}
|
||||
@@ -910,34 +943,60 @@ func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
|
||||
|
||||
func setEthash(ctx *cli.Context, cfg *eth.Config) {
|
||||
if ctx.GlobalIsSet(EthashCacheDirFlag.Name) {
|
||||
cfg.EthashCacheDir = ctx.GlobalString(EthashCacheDirFlag.Name)
|
||||
cfg.Ethash.CacheDir = ctx.GlobalString(EthashCacheDirFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(EthashDatasetDirFlag.Name) {
|
||||
cfg.EthashDatasetDir = ctx.GlobalString(EthashDatasetDirFlag.Name)
|
||||
cfg.Ethash.DatasetDir = ctx.GlobalString(EthashDatasetDirFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(EthashCachesInMemoryFlag.Name) {
|
||||
cfg.EthashCachesInMem = ctx.GlobalInt(EthashCachesInMemoryFlag.Name)
|
||||
cfg.Ethash.CachesInMem = ctx.GlobalInt(EthashCachesInMemoryFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(EthashCachesOnDiskFlag.Name) {
|
||||
cfg.EthashCachesOnDisk = ctx.GlobalInt(EthashCachesOnDiskFlag.Name)
|
||||
cfg.Ethash.CachesOnDisk = ctx.GlobalInt(EthashCachesOnDiskFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(EthashDatasetsInMemoryFlag.Name) {
|
||||
cfg.EthashDatasetsInMem = ctx.GlobalInt(EthashDatasetsInMemoryFlag.Name)
|
||||
cfg.Ethash.DatasetsInMem = ctx.GlobalInt(EthashDatasetsInMemoryFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(EthashDatasetsOnDiskFlag.Name) {
|
||||
cfg.EthashDatasetsOnDisk = ctx.GlobalInt(EthashDatasetsOnDiskFlag.Name)
|
||||
cfg.Ethash.DatasetsOnDisk = ctx.GlobalInt(EthashDatasetsOnDiskFlag.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func checkExclusive(ctx *cli.Context, flags ...cli.Flag) {
|
||||
// checkExclusive verifies that only a single isntance of the provided flags was
|
||||
// set by the user. Each flag might optionally be followed by a string type to
|
||||
// specialize it further.
|
||||
func checkExclusive(ctx *cli.Context, args ...interface{}) {
|
||||
set := make([]string, 0, 1)
|
||||
for _, flag := range flags {
|
||||
for i := 0; i < len(args); i++ {
|
||||
// Make sure the next argument is a flag and skip if not set
|
||||
flag, ok := args[i].(cli.Flag)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("invalid argument, not cli.Flag type: %T", args[i]))
|
||||
}
|
||||
// Check if next arg extends current and expand its name if so
|
||||
name := flag.GetName()
|
||||
|
||||
if i+1 < len(args) {
|
||||
switch option := args[i+1].(type) {
|
||||
case string:
|
||||
// Extended flag, expand the name and shift the arguments
|
||||
if ctx.GlobalString(flag.GetName()) == option {
|
||||
name += "=" + option
|
||||
}
|
||||
i++
|
||||
|
||||
case cli.Flag:
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid argument, not cli.Flag or string extension: %T", args[i+1]))
|
||||
}
|
||||
}
|
||||
// Mark the flag if it's set
|
||||
if ctx.GlobalIsSet(flag.GetName()) {
|
||||
set = append(set, "--"+flag.GetName())
|
||||
set = append(set, "--"+name)
|
||||
}
|
||||
}
|
||||
if len(set) > 1 {
|
||||
Fatalf("flags %v can't be used at the same time", strings.Join(set, ", "))
|
||||
Fatalf("Flags %v can't be used at the same time", strings.Join(set, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -956,6 +1015,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
// Avoid conflicting network flags
|
||||
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
||||
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
|
||||
checkExclusive(ctx, LightServFlag, LightModeFlag)
|
||||
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
||||
|
||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
setEtherbase(ctx, ks, cfg)
|
||||
@@ -981,11 +1042,19 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name)
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(CacheFlag.Name) {
|
||||
cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name)
|
||||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheDatabaseFlag.Name) {
|
||||
cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
|
||||
}
|
||||
cfg.DatabaseHandles = makeDatabaseHandles()
|
||||
|
||||
if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
|
||||
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
|
||||
}
|
||||
cfg.NoPruning = ctx.GlobalString(GCModeFlag.Name) == "archive"
|
||||
|
||||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
|
||||
cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
|
||||
}
|
||||
if ctx.GlobalIsSet(MinerThreadsFlag.Name) {
|
||||
cfg.MinerThreads = ctx.GlobalInt(MinerThreadsFlag.Name)
|
||||
}
|
||||
@@ -1076,9 +1145,9 @@ func RegisterEthService(stack *node.Node, cfg *eth.Config) {
|
||||
}
|
||||
|
||||
// RegisterDashboardService adds a dashboard to the stack.
|
||||
func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config) {
|
||||
func RegisterDashboardService(stack *node.Node, cfg *dashboard.Config, commit string) {
|
||||
stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return dashboard.New(cfg)
|
||||
return dashboard.New(cfg, commit)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1111,13 +1180,13 @@ func RegisterEthStatsService(stack *node.Node, url string) {
|
||||
// SetupNetwork configures the system for either the main net or some test network.
|
||||
func SetupNetwork(ctx *cli.Context) {
|
||||
// TODO(fjl): move target gas limit into config
|
||||
params.TargetGasLimit = new(big.Int).SetUint64(ctx.GlobalUint64(TargetGasLimitFlag.Name))
|
||||
params.TargetGasLimit = ctx.GlobalUint64(TargetGasLimitFlag.Name)
|
||||
}
|
||||
|
||||
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
|
||||
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
|
||||
var (
|
||||
cache = ctx.GlobalInt(CacheFlag.Name)
|
||||
cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100
|
||||
handles = makeDatabaseHandles()
|
||||
)
|
||||
name := "chaindata"
|
||||
@@ -1159,14 +1228,29 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
|
||||
} else {
|
||||
engine = ethash.NewFaker()
|
||||
if !ctx.GlobalBool(FakePoWFlag.Name) {
|
||||
engine = ethash.New(
|
||||
stack.ResolvePath(eth.DefaultConfig.EthashCacheDir), eth.DefaultConfig.EthashCachesInMem, eth.DefaultConfig.EthashCachesOnDisk,
|
||||
stack.ResolvePath(eth.DefaultConfig.EthashDatasetDir), eth.DefaultConfig.EthashDatasetsInMem, eth.DefaultConfig.EthashDatasetsOnDisk,
|
||||
)
|
||||
engine = ethash.New(ethash.Config{
|
||||
CacheDir: stack.ResolvePath(eth.DefaultConfig.Ethash.CacheDir),
|
||||
CachesInMem: eth.DefaultConfig.Ethash.CachesInMem,
|
||||
CachesOnDisk: eth.DefaultConfig.Ethash.CachesOnDisk,
|
||||
DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir),
|
||||
DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem,
|
||||
DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk,
|
||||
})
|
||||
}
|
||||
}
|
||||
if gcmode := ctx.GlobalString(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
|
||||
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
|
||||
}
|
||||
cache := &core.CacheConfig{
|
||||
Disabled: ctx.GlobalString(GCModeFlag.Name) == "archive",
|
||||
TrieNodeLimit: eth.DefaultConfig.TrieCache,
|
||||
TrieTimeLimit: eth.DefaultConfig.TrieTimeout,
|
||||
}
|
||||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
|
||||
cache.TrieNodeLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
|
||||
}
|
||||
vmcfg := vm.Config{EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name)}
|
||||
chain, err = core.NewBlockChain(chainDb, config, engine, vmcfg)
|
||||
chain, err = core.NewBlockChain(chainDb, cache, config, engine, vmcfg)
|
||||
if err != nil {
|
||||
Fatalf("Can't create BlockChain: %v", err)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/whisper/mailserver"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv5"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
@@ -61,15 +61,17 @@ var (
|
||||
|
||||
// encryption
|
||||
var (
|
||||
symKey []byte
|
||||
pub *ecdsa.PublicKey
|
||||
asymKey *ecdsa.PrivateKey
|
||||
nodeid *ecdsa.PrivateKey
|
||||
topic whisper.TopicType
|
||||
asymKeyID string
|
||||
filterID string
|
||||
symPass string
|
||||
msPassword string
|
||||
symKey []byte
|
||||
pub *ecdsa.PublicKey
|
||||
asymKey *ecdsa.PrivateKey
|
||||
nodeid *ecdsa.PrivateKey
|
||||
topic whisper.TopicType
|
||||
|
||||
asymKeyID string
|
||||
asymFilterID string
|
||||
symFilterID string
|
||||
symPass string
|
||||
msPassword string
|
||||
)
|
||||
|
||||
// cmd arguments
|
||||
@@ -363,13 +365,22 @@ func configureNode() {
|
||||
}
|
||||
}
|
||||
|
||||
filter := whisper.Filter{
|
||||
symFilter := whisper.Filter{
|
||||
KeySym: symKey,
|
||||
Topics: [][]byte{topic[:]},
|
||||
AllowP2P: p2pAccept,
|
||||
}
|
||||
symFilterID, err = shh.Subscribe(&symFilter)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to install filter: %s", err)
|
||||
}
|
||||
|
||||
asymFilter := whisper.Filter{
|
||||
KeyAsym: asymKey,
|
||||
Topics: [][]byte{topic[:]},
|
||||
AllowP2P: p2pAccept,
|
||||
}
|
||||
filterID, err = shh.Subscribe(&filter)
|
||||
asymFilterID, err = shh.Subscribe(&asymFilter)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to install filter: %s", err)
|
||||
}
|
||||
@@ -522,9 +533,14 @@ func sendMsg(payload []byte) common.Hash {
|
||||
}
|
||||
|
||||
func messageLoop() {
|
||||
f := shh.GetFilter(filterID)
|
||||
if f == nil {
|
||||
utils.Fatalf("filter is not installed")
|
||||
sf := shh.GetFilter(symFilterID)
|
||||
if sf == nil {
|
||||
utils.Fatalf("symmetric filter is not installed")
|
||||
}
|
||||
|
||||
af := shh.GetFilter(asymFilterID)
|
||||
if af == nil {
|
||||
utils.Fatalf("asymmetric filter is not installed")
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
@@ -532,7 +548,16 @@ func messageLoop() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
messages := f.Retrieve()
|
||||
messages := sf.Retrieve()
|
||||
for _, msg := range messages {
|
||||
if *fileExMode || len(msg.Payload) > 2048 {
|
||||
writeMessageToFile(*argSaveDir, msg)
|
||||
} else {
|
||||
printMessageInfo(msg)
|
||||
}
|
||||
}
|
||||
|
||||
messages = af.Retrieve()
|
||||
for _, msg := range messages {
|
||||
if *fileExMode || len(msg.Payload) > 2048 {
|
||||
writeMessageToFile(*argSaveDir, msg)
|
||||
@@ -601,7 +626,7 @@ func requestExpiredMessagesLoop() {
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to save symmetric key for mail request: %s", err)
|
||||
}
|
||||
peerID = extractIdFromEnode(*argEnode)
|
||||
peerID = extractIDFromEnode(*argEnode)
|
||||
shh.AllowP2PMessagesFromPeer(peerID)
|
||||
|
||||
for {
|
||||
@@ -652,7 +677,7 @@ func requestExpiredMessagesLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
func extractIdFromEnode(s string) []byte {
|
||||
func extractIDFromEnode(s string) []byte {
|
||||
n, err := discover.ParseNode(s)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to parse enode: %s", err)
|
||||
|
||||
@@ -40,7 +40,7 @@ func fastXORBytes(dst, a, b []byte) int {
|
||||
dw[i] = aw[i] ^ bw[i]
|
||||
}
|
||||
}
|
||||
for i := (n - n%wordSize); i < n; i++ {
|
||||
for i := n - n%wordSize; i < n; i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
return n
|
||||
@@ -84,7 +84,7 @@ func fastANDBytes(dst, a, b []byte) int {
|
||||
dw[i] = aw[i] & bw[i]
|
||||
}
|
||||
}
|
||||
for i := (n - n%wordSize); i < n; i++ {
|
||||
for i := n - n%wordSize; i < n; i++ {
|
||||
dst[i] = a[i] & b[i]
|
||||
}
|
||||
return n
|
||||
@@ -128,7 +128,7 @@ func fastORBytes(dst, a, b []byte) int {
|
||||
dw[i] = aw[i] | bw[i]
|
||||
}
|
||||
}
|
||||
for i := (n - n%wordSize); i < n; i++ {
|
||||
for i := n - n%wordSize; i < n; i++ {
|
||||
dst[i] = a[i] | b[i]
|
||||
}
|
||||
return n
|
||||
@@ -168,7 +168,7 @@ func fastTestBytes(p []byte) bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := (n - n%wordSize); i < n; i++ {
|
||||
for i := n - n%wordSize; i < n; i++ {
|
||||
if p[i] != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
// Package common contains various helper functions.
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
)
|
||||
import "encoding/hex"
|
||||
|
||||
func ToHex(b []byte) string {
|
||||
hex := Bytes2Hex(b)
|
||||
@@ -35,12 +33,11 @@ func FromHex(s string) []byte {
|
||||
if s[0:2] == "0x" || s[0:2] == "0X" {
|
||||
s = s[2:]
|
||||
}
|
||||
if len(s)%2 == 1 {
|
||||
s = "0" + s
|
||||
}
|
||||
return Hex2Bytes(s)
|
||||
}
|
||||
return nil
|
||||
if len(s)%2 == 1 {
|
||||
s = "0" + s
|
||||
}
|
||||
return Hex2Bytes(s)
|
||||
}
|
||||
|
||||
// Copy bytes
|
||||
@@ -56,14 +53,24 @@ func CopyBytes(b []byte) (copiedBytes []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func HasHexPrefix(str string) bool {
|
||||
l := len(str)
|
||||
return l >= 2 && str[0:2] == "0x"
|
||||
func hasHexPrefix(str string) bool {
|
||||
return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')
|
||||
}
|
||||
|
||||
func IsHex(str string) bool {
|
||||
l := len(str)
|
||||
return l >= 4 && l%2 == 0 && str[0:2] == "0x"
|
||||
func isHexCharacter(c byte) bool {
|
||||
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
|
||||
}
|
||||
|
||||
func isHex(str string) bool {
|
||||
if len(str)%2 != 0 {
|
||||
return false
|
||||
}
|
||||
for _, c := range []byte(str) {
|
||||
if !isHexCharacter(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Bytes2Hex(d []byte) string {
|
||||
|
||||
@@ -34,19 +34,6 @@ func (s *BytesSuite) TestCopyBytes(c *checker.C) {
|
||||
c.Assert(res1, checker.DeepEquals, exp1)
|
||||
}
|
||||
|
||||
func (s *BytesSuite) TestIsHex(c *checker.C) {
|
||||
data1 := "a9e67e"
|
||||
exp1 := false
|
||||
res1 := IsHex(data1)
|
||||
c.Assert(res1, checker.DeepEquals, exp1)
|
||||
|
||||
data2 := "0xa9e67e00"
|
||||
exp2 := true
|
||||
res2 := IsHex(data2)
|
||||
c.Assert(res2, checker.DeepEquals, exp2)
|
||||
|
||||
}
|
||||
|
||||
func (s *BytesSuite) TestLeftPadBytes(c *checker.C) {
|
||||
val1 := []byte{1, 2, 3, 4}
|
||||
exp1 := []byte{0, 0, 0, 0, 1, 2, 3, 4}
|
||||
@@ -74,7 +61,28 @@ func TestFromHex(t *testing.T) {
|
||||
expected := []byte{1}
|
||||
result := FromHex(input)
|
||||
if !bytes.Equal(expected, result) {
|
||||
t.Errorf("Expected % x got % x", expected, result)
|
||||
t.Errorf("Expected %x got %x", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsHex(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
ok bool
|
||||
}{
|
||||
{"", true},
|
||||
{"0", false},
|
||||
{"00", true},
|
||||
{"a9e67e", true},
|
||||
{"A9E67E", true},
|
||||
{"0xa9e67e", false},
|
||||
{"a9e67e001", false},
|
||||
{"0xHELLO_MY_NAME_IS_STEVEN_@#$^&*", false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if ok := isHex(test.input); ok != test.ok {
|
||||
t.Errorf("isHex(%q) = %v, want %v", test.input, ok, test.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +91,15 @@ func TestFromHexOddLength(t *testing.T) {
|
||||
expected := []byte{1}
|
||||
result := FromHex(input)
|
||||
if !bytes.Equal(expected, result) {
|
||||
t.Errorf("Expected % x got % x", expected, result)
|
||||
t.Errorf("Expected %x got %x", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoPrefixShortHexOddLength(t *testing.T) {
|
||||
input := "1"
|
||||
expected := []byte{1}
|
||||
result := FromHex(input)
|
||||
if !bytes.Equal(expected, result) {
|
||||
t.Errorf("Expected %x got %x", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// 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 go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser 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,
|
||||
// The go-ethereum library is distributed in the hope that 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.
|
||||
// GNU Lesser 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/>.
|
||||
// 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 freebsd
|
||||
|
||||
package utils
|
||||
package fdlimit
|
||||
|
||||
import "syscall"
|
||||
|
||||
@@ -24,9 +24,9 @@ import "syscall"
|
||||
// but Rlimit fields have type int64 on FreeBSD so it needs
|
||||
// an extra conversion.
|
||||
|
||||
// raiseFdLimit tries to maximize the file descriptor allowance of this process
|
||||
// Raise tries to maximize the file descriptor allowance of this process
|
||||
// to the maximum hard-limit allowed by the OS.
|
||||
func raiseFdLimit(max uint64) error {
|
||||
func Raise(max uint64) error {
|
||||
// Get the current limit
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
@@ -43,12 +43,22 @@ func raiseFdLimit(max uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFdLimit retrieves the number of file descriptors allowed to be opened by this
|
||||
// Current retrieves the number of file descriptors allowed to be opened by this
|
||||
// process.
|
||||
func getFdLimit() (int, error) {
|
||||
func Current() (int, error) {
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(limit.Cur), nil
|
||||
}
|
||||
|
||||
// Maximum retrieves the maximum number of file descriptors this process is
|
||||
// allowed to request for itself.
|
||||
func Maximum() (int, error) {
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(limit.Max), nil
|
||||
}
|
||||
45
common/fdlimit/fdlimit_test.go
Normal file
45
common/fdlimit/fdlimit_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package fdlimit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
|
||||
// per this process can be retrieved.
|
||||
func TestFileDescriptorLimits(t *testing.T) {
|
||||
target := 4096
|
||||
hardlimit, err := Maximum()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hardlimit < target {
|
||||
t.Skip(fmt.Sprintf("system limit is less than desired test target: %d < %d", hardlimit, target))
|
||||
}
|
||||
|
||||
if limit, err := Current(); err != nil || limit <= 0 {
|
||||
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
|
||||
}
|
||||
if err := Raise(uint64(target)); err != nil {
|
||||
t.Fatalf("failed to raise file allowance")
|
||||
}
|
||||
if limit, err := Current(); err != nil || limit < target {
|
||||
t.Fatalf("failed to retrieve raised descriptor limit (have %v, want %v): %v", limit, target, err)
|
||||
}
|
||||
}
|
||||
60
common/fdlimit/fdlimit_unix.go
Normal file
60
common/fdlimit/fdlimit_unix.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build linux darwin netbsd openbsd solaris
|
||||
|
||||
package fdlimit
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Raise tries to maximize the file descriptor allowance of this process
|
||||
// to the maximum hard-limit allowed by the OS.
|
||||
func Raise(max uint64) error {
|
||||
// Get the current limit
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return err
|
||||
}
|
||||
// Try to update the limit to the max allowance
|
||||
limit.Cur = limit.Max
|
||||
if limit.Cur > max {
|
||||
limit.Cur = max
|
||||
}
|
||||
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Current retrieves the number of file descriptors allowed to be opened by this
|
||||
// process.
|
||||
func Current() (int, error) {
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(limit.Cur), nil
|
||||
}
|
||||
|
||||
// Maximum retrieves the maximum number of file descriptors this process is
|
||||
// allowed to request for itself.
|
||||
func Maximum() (int, error) {
|
||||
var limit syscall.Rlimit
|
||||
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(limit.Max), nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user