Compare commits
188 Commits
Author | SHA1 | Date | |
---|---|---|---|
8f0db69762 | |||
7a5843de31 | |||
0cc6397195 | |||
dc2b23c869 | |||
6999f1da6b | |||
95bfedd599 | |||
42e4e18667 | |||
9bdbaf459a | |||
421df866cd | |||
dfc63c49c7 | |||
e44b2dc881 | |||
fed692f67e | |||
99a0c76435 | |||
5ca5ccf90c | |||
c4ed34f008 | |||
0ab7e90cbb | |||
bdbfe572f1 | |||
c4e4baf668 | |||
86493f9103 | |||
6c672a55c0 | |||
48709d5340 | |||
65da8f601f | |||
2c6214e846 | |||
0398075ced | |||
d1696dbf07 | |||
626604e86d | |||
9eb2873a9c | |||
08a7cd74da | |||
35d479b6d3 | |||
5f55d95aea | |||
c2eea6306e | |||
1d6b65cd84 | |||
1b2941cd56 | |||
b8c0883770 | |||
14bad7e212 | |||
8c20fe17bd | |||
a0cc73a27a | |||
682c4531af | |||
5c3051e6fa | |||
3dd46bc884 | |||
e44d50fb52 | |||
5d9ea439b3 | |||
d0668838b9 | |||
da776556d0 | |||
f2e8759d10 | |||
98095efe88 | |||
b7e3dfc5a2 | |||
3e1dbc3ca7 | |||
adb065a328 | |||
c793cb3385 | |||
3eef19598e | |||
f4aebd4c8d | |||
98be7cd833 | |||
eaf706b73c | |||
b170a80cdc | |||
aefffc9ed8 | |||
f31a3a251a | |||
a9c94cbf48 | |||
667a386d87 | |||
d2089e46f8 | |||
be29e41334 | |||
47965930a1 | |||
bc6c4a337c | |||
f7fdfa4eac | |||
0405f728c6 | |||
63c5a46b82 | |||
c89fa789b7 | |||
39f1d909d1 | |||
71b577f839 | |||
a93d63d576 | |||
7fb72dbcbf | |||
688fbab5d5 | |||
0f036f6209 | |||
71a89b7c75 | |||
ecb8e23e88 | |||
058c5fe960 | |||
a29bdf547c | |||
44b912ec64 | |||
3d69970c15 | |||
8b90a49f3d | |||
c046126c87 | |||
cd134178f7 | |||
4918c820c6 | |||
5904d58a96 | |||
7c90a2e42e | |||
c39de61a0a | |||
af53767e16 | |||
7632acf6b4 | |||
9ccb70da7b | |||
8fefee7132 | |||
7a4073a758 | |||
ab522d3bc7 | |||
c45c424073 | |||
8d2775e3d7 | |||
170036289b | |||
8ebbd9b7c7 | |||
7df36e5ec1 | |||
5fb29fd45f | |||
90beb6112e | |||
efa2b3da7e | |||
e3b3c298df | |||
3752507a11 | |||
a269a713d6 | |||
27df30f30f | |||
311f5a0ed1 | |||
68ae6b52e9 | |||
1776c717bf | |||
0f6e3e873a | |||
7a7a5acc9f | |||
66d74dfb75 | |||
b950a2977c | |||
8ea3c88e44 | |||
94ad694a26 | |||
4c6953606e | |||
fc0638f9d8 | |||
4b11f207cb | |||
7e5c49cafa | |||
efcfa2209b | |||
aa18aad7ad | |||
594328c112 | |||
2e6b9c141b | |||
b38cea6654 | |||
dd083aa34e | |||
f213a9d8e8 | |||
6826b2040f | |||
2a3657d8d9 | |||
566af6ef92 | |||
290e851f57 | |||
8f96d66241 | |||
5782164a35 | |||
27f657478f | |||
756b62988c | |||
f61e203c10 | |||
56ed6152a1 | |||
dc7f202ecd | |||
1d42061e2c | |||
b6135a72dd | |||
7d59c5c58d | |||
8aa4597c9e | |||
57ba1824ac | |||
c89f4352d0 | |||
6a00a3ade1 | |||
f821b0188a | |||
d79f2f2656 | |||
130bccc763 | |||
ae9ed5c420 | |||
a1c201a5ac | |||
844e911129 | |||
4b9de75623 | |||
2d7d7ef2fe | |||
14d5033c9d | |||
d52a693f80 | |||
258cc73ea9 | |||
79b7b5eeaa | |||
b4fbcd5060 | |||
488528e9e4 | |||
8110671960 | |||
32bb280179 | |||
4536b993ff | |||
586eddfd09 | |||
d46da273c6 | |||
ecd7199c43 | |||
1c20313a6a | |||
cfa999f006 | |||
c74a575725 | |||
9672a62a38 | |||
572da73d4d | |||
0f722df2d9 | |||
1b77d5090d | |||
e62c2aeb1b | |||
4880868c88 | |||
c3d5250473 | |||
e0dc45fce2 | |||
48cc36ce83 | |||
123aa659e4 | |||
cdcbb2f160 | |||
db62979514 | |||
5137c04ccf | |||
a20d3fc362 | |||
3d6d828caf | |||
6a543607ef | |||
c1a4dcfc87 | |||
70b8b54cd2 | |||
c88c89fd9e | |||
b06f44ecc2 | |||
87ae0df476 | |||
5127ec10cb | |||
18580e152c |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@ -8,7 +8,7 @@ and help.
|
||||
## Contributing
|
||||
|
||||
If you'd like to contribute to go-ethereum please fork, fix, commit and
|
||||
send a pull request. Commits who do not comply with the coding standards
|
||||
send a pull request. Commits which do not comply with the coding standards
|
||||
are ignored (use gofmt!). If you send pull requests make absolute sure that you
|
||||
commit on the `develop` branch and that you do not merge to master.
|
||||
Commits that are directly based on master are simply ignored.
|
||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -23,17 +23,11 @@ Godeps/_workspace/bin
|
||||
.project
|
||||
.settings
|
||||
|
||||
deploy/osx/Mist.app
|
||||
deploy/osx/Mist\ Installer.dmg
|
||||
cmd/mist/assets/ext/ethereum.js/
|
||||
|
||||
# used by the Makefile
|
||||
/build/_workspace/
|
||||
/build/bin/
|
||||
/geth*.zip
|
||||
|
||||
# travis
|
||||
profile.tmp
|
||||
profile.cov
|
||||
|
||||
# vagrant
|
||||
.vagrant
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "cmd/mist/assets/ext/ethereum.js"]
|
||||
path = cmd/mist/assets/ext/ethereum.js
|
||||
url = https://github.com/ethereum/web3.js
|
60
.travis.yml
60
.travis.yml
@ -1,29 +1,45 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.4.2
|
||||
install:
|
||||
# - go get code.google.com/p/go.tools/cmd/goimports
|
||||
# - go get github.com/golang/lint/golint
|
||||
# - go get golang.org/x/tools/cmd/vet
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
before_script:
|
||||
# - gofmt -l -w .
|
||||
# - goimports -l -w .
|
||||
# - golint .
|
||||
# - go vet ./...
|
||||
# - go test -race ./...
|
||||
script:
|
||||
- make travis-test-with-coverage
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
env:
|
||||
global:
|
||||
- secure: "U2U1AmkU4NJBgKR/uUAebQY87cNL0+1JHjnLOmmXwxYYyj5ralWb1aSuSH3qSXiT93qLBmtaUkuv9fberHVqrbAeVlztVdUsKAq7JMQH+M99iFkC9UiRMqHmtjWJ0ok4COD1sRYixxi21wb/JrMe3M1iL4QJVS61iltjHhVdM64="
|
||||
go_import_path: github.com/ethereum/go-ethereum
|
||||
sudo: false
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.4.2
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.5.4
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.6.2
|
||||
- os: osx
|
||||
go: 1.6.2
|
||||
|
||||
# This builder does the PPA upload (and nothing else).
|
||||
- os: linux
|
||||
dist: trusty
|
||||
go: 1.6.2
|
||||
env: PPA
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- devscripts
|
||||
- debhelper
|
||||
- dput
|
||||
script:
|
||||
- go run build/ci.go travis-debsrc
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage -vet
|
||||
after_success:
|
||||
# - go run build/ci.go archive -type tar
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/e09ccdce1048c5e03445
|
||||
on_success: change
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_start: false
|
||||
|
21
Godeps/Godeps.json
generated
21
Godeps/Godeps.json
generated
@ -1,6 +1,7 @@
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/go-ethereum",
|
||||
"GoVersion": "go1.5.2",
|
||||
"GodepVersion": "v74",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
@ -13,19 +14,14 @@
|
||||
"ImportPath": "github.com/cespare/cp",
|
||||
"Rev": "165db2f241fd235aec29ba6d9b1ccd5f1c14637c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/codegangsta/cli",
|
||||
"Comment": "1.2.0-215-g0ab42fd",
|
||||
"Rev": "0ab42fd482c27cf2c95e7794ad3bb2082c2ab2d7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/ethereum/ethash",
|
||||
"Comment": "v23.1-245-g25b32de",
|
||||
"Rev": "25b32de0c0271065c28c3719c2bfe86959d72f0c"
|
||||
"Comment": "v23.1-247-g2e80de5",
|
||||
"Rev": "2e80de5022370cfe632195b1720db52d07ff8a77"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/fatih/color",
|
||||
@ -121,7 +117,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rjeczalik/notify",
|
||||
"Rev": "5dd6205716539662f8f14ab513552b41eab69d5d"
|
||||
"Rev": "f627deca7a510d96f0ef9388f2d0e8b16d21f87f"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/robertkrimen/otto",
|
||||
@ -155,6 +151,10 @@
|
||||
"ImportPath": "github.com/rs/cors",
|
||||
"Rev": "5950cf11d77f8a61b432a25dd4d444b4ced01379"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/rs/xhandler",
|
||||
"Rev": "d9d9599b6aaf6a058cb7b1f48291ded2cbd13390"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||
"Rev": "917f41c560270110ceb73c5b38be2a9127387071"
|
||||
@ -319,6 +319,11 @@
|
||||
{
|
||||
"ImportPath": "gopkg.in/karalabe/cookiejar.v2/collections/prque",
|
||||
"Rev": "8dcd6a7f4951f6ff3ee9cbb919a06d8925822e57"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/urfave/cli.v1",
|
||||
"Comment": "v1.17.0",
|
||||
"Rev": "01857ac33766ce0c93856370626f9799281c14f4"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
14
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
14
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
@ -1,14 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
: ${PROG:=$(basename ${BASH_SOURCE})}
|
||||
|
||||
_cli_bash_autocomplete() {
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _cli_bash_autocomplete $PROG
|
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
5
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
generated
vendored
@ -1,5 +0,0 @@
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
script_dir=$(dirname $0)
|
||||
source ${script_dir}/bash_autocomplete
|
0
Godeps/_workspace/src/github.com/ethereum/ethash/setup.py
generated
vendored
Normal file → Executable file
0
Godeps/_workspace/src/github.com/ethereum/ethash/setup.py
generated
vendored
Normal file → Executable file
29
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/endian.h
generated
vendored
29
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/endian.h
generated
vendored
@ -19,7 +19,7 @@
|
||||
# define BYTE_ORDER LITTLE_ENDIAN
|
||||
#elif defined( __QNXNTO__ ) && defined( __BIGENDIAN__ )
|
||||
# define BIG_ENDIAN 1234
|
||||
# define BYTE_ORDER BIG_ENDIAN
|
||||
# define BYTE_ORDER BIG_ENDIAN
|
||||
#else
|
||||
# include <endian.h>
|
||||
#endif
|
||||
@ -59,21 +59,20 @@
|
||||
|
||||
#define fix_endian32(dst_, src_) dst_ = ethash_swap_u32(src_)
|
||||
#define fix_endian32_same(val_) val_ = ethash_swap_u32(val_)
|
||||
#define fix_endian64(dst_, src_) dst_ = ethash_swap_u64(src_
|
||||
#define fix_endian64(dst_, src_) dst_ = ethash_swap_u64(src_)
|
||||
#define fix_endian64_same(val_) val_ = ethash_swap_u64(val_)
|
||||
#define fix_endian_arr32(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_), ++i_) { \
|
||||
arr_[i_] = ethash_swap_u32(arr_[i_]); \
|
||||
} \
|
||||
while (0)
|
||||
#define fix_endian_arr64(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_), ++i_) { \
|
||||
arr_[i_] = ethash_swap_u64(arr_[i_]); \
|
||||
} \
|
||||
while (0) \
|
||||
|
||||
#define fix_endian_arr32(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_); ++i_) { \
|
||||
arr_[i_] = ethash_swap_u32(arr_[i_]); \
|
||||
} \
|
||||
} while (0)
|
||||
#define fix_endian_arr64(arr_, size_) \
|
||||
do { \
|
||||
for (unsigned i_ = 0; i_ < (size_); ++i_) { \
|
||||
arr_[i_] = ethash_swap_u64(arr_[i_]); \
|
||||
} \
|
||||
} while (0)
|
||||
#else
|
||||
# error "endian not supported"
|
||||
#endif // BYTE_ORDER
|
||||
|
2
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/internal.c
generated
vendored
2
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/internal.c
generated
vendored
@ -257,7 +257,7 @@ static bool ethash_hash(
|
||||
void ethash_quick_hash(
|
||||
ethash_h256_t* return_hash,
|
||||
ethash_h256_t const* header_hash,
|
||||
uint64_t const nonce,
|
||||
uint64_t nonce,
|
||||
ethash_h256_t const* mix_hash
|
||||
)
|
||||
{
|
||||
|
3
Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml
generated
vendored
3
Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml
generated
vendored
@ -21,10 +21,9 @@ env:
|
||||
- PATH=$HOME/bin:$PATH
|
||||
|
||||
install:
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go tool vet -all .
|
||||
- "(go version | grep -q 1.4) || go tool vet -all ."
|
||||
- go install $GOFLAGS ./...
|
||||
- go test -v -race $GOFLAGS ./...
|
||||
|
1
Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml
generated
vendored
1
Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml
generated
vendored
@ -11,7 +11,6 @@ environment:
|
||||
|
||||
install:
|
||||
- go version
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get -v -t ./...
|
||||
|
||||
build_script:
|
||||
|
2
Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go
generated
vendored
2
Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go
generated
vendored
@ -133,7 +133,7 @@ func (w *watch) Dispatch(ev []FSEvent) {
|
||||
ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
|
||||
if ev[i].Flags&failure != 0 {
|
||||
// TODO(rjeczalik): missing error handling
|
||||
panic("unhandled error: " + Event(ev[i].Flags).String())
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(ev[i].Path, w.path) {
|
||||
continue
|
||||
|
7
Godeps/_workspace/src/github.com/rs/xhandler/.travis.yml
generated
vendored
7
Godeps/_workspace/src/github.com/rs/xhandler/.travis.yml
generated
vendored
@ -1,7 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
19
Godeps/_workspace/src/github.com/rs/xhandler/LICENSE
generated
vendored
19
Godeps/_workspace/src/github.com/rs/xhandler/LICENSE
generated
vendored
@ -1,19 +0,0 @@
|
||||
Copyright (c) 2015 Olivier Poitrey <rs@dailymotion.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
134
Godeps/_workspace/src/github.com/rs/xhandler/README.md
generated
vendored
134
Godeps/_workspace/src/github.com/rs/xhandler/README.md
generated
vendored
@ -1,134 +0,0 @@
|
||||
# XHandler
|
||||
|
||||
[](https://godoc.org/github.com/rs/xhandler) [](https://raw.githubusercontent.com/rs/xhandler/master/LICENSE) [](https://travis-ci.org/rs/xhandler) [](http://gocover.io/github.com/rs/xhandler)
|
||||
|
||||
XHandler is a bridge between [net/context](https://godoc.org/golang.org/x/net/context) and `http.Handler`.
|
||||
|
||||
It lets you enforce `net/context` in your handlers without sacrificing compatibility with existing `http.Handlers` nor imposing a specific router.
|
||||
|
||||
Thanks to `net/context` deadline management, `xhandler` is able to enforce a per request deadline and will cancel the context when the client closes the connection unexpectedly.
|
||||
|
||||
You may create your own `net/context` aware handler pretty much the same way as you would do with http.Handler.
|
||||
|
||||
Read more about xhandler on [Dailymotion engineering blog](http://engineering.dailymotion.com/our-way-to-go/).
|
||||
|
||||
## Installing
|
||||
|
||||
go get -u github.com/rs/xhandler
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/cors"
|
||||
"github.com/rs/xhandler"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type myMiddleware struct {
|
||||
next xhandler.HandlerC
|
||||
}
|
||||
|
||||
func (h myMiddleware) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
ctx = context.WithValue(ctx, "test", "World")
|
||||
h.next.ServeHTTPC(ctx, w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := xhandler.Chain{}
|
||||
|
||||
// Add close notifier handler so context is cancelled when the client closes
|
||||
// the connection
|
||||
c.UseC(xhandler.CloseHandler)
|
||||
|
||||
// Add timeout handler
|
||||
c.UseC(xhandler.TimeoutHandler(2 * time.Second))
|
||||
|
||||
// Middleware putting something in the context
|
||||
c.UseC(func(next xhandler.HandlerC) xhandler.HandlerC {
|
||||
return myMiddleware{next: next}
|
||||
})
|
||||
|
||||
// Mix it with a non-context-aware middleware handler
|
||||
c.Use(cors.Default().Handler)
|
||||
|
||||
// Final handler (using handlerFuncC), reading from the context
|
||||
xh := xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
value := ctx.Value("test").(string)
|
||||
w.Write([]byte("Hello " + value))
|
||||
})
|
||||
|
||||
// Bridge context aware handlers with http.Handler using xhandler.Handle()
|
||||
http.Handle("/test", c.Handler(xh))
|
||||
|
||||
if err := http.ListenAndServe(":8080", nil); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using xmux
|
||||
|
||||
Xhandler comes with an optional context aware [muxer](https://github.com/rs/xmux) forked from [httprouter](https://github.com/julienschmidt/httprouter):
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/rs/xhandler"
|
||||
"github.com/rs/xmux"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func main() {
|
||||
c := xhandler.Chain{}
|
||||
|
||||
// Append a context-aware middleware handler
|
||||
c.UseC(xhandler.CloseHandler)
|
||||
|
||||
// Another context-aware middleware handler
|
||||
c.UseC(xhandler.TimeoutHandler(2 * time.Second))
|
||||
|
||||
mux := xmux.New()
|
||||
|
||||
// Use c.Handler to terminate the chain with your final handler
|
||||
mux.GET("/welcome/:name", xhandler.HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "Welcome %s!", xmux.Params(ctx).Get("name"))
|
||||
}))
|
||||
|
||||
if err := http.ListenAndServe(":8080", c.Handler(mux)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [xmux](https://github.com/rs/xmux) for more examples.
|
||||
|
||||
## Context Aware Middleware
|
||||
|
||||
Here is a list of `net/context` aware middleware handlers implementing `xhandler.HandlerC` interface.
|
||||
|
||||
Feel free to put up a PR linking your middleware if you have built one:
|
||||
|
||||
| Middleware | Author | Description |
|
||||
| ---------- | ------ | ----------- |
|
||||
| [xmux](https://github.com/rs/xmux) | [Olivier Poitrey](https://github.com/rs) | HTTP request muxer |
|
||||
| [xlog](https://github.com/rs/xlog) | [Olivier Poitrey](https://github.com/rs) | HTTP handler logger |
|
||||
| [xstats](https://github.com/rs/xstats) | [Olivier Poitrey](https://github.com/rs) | A generic client for service instrumentation |
|
||||
| [xaccess](https://github.com/rs/xaccess) | [Olivier Poitrey](https://github.com/rs) | HTTP handler access logger with [xlog](https://github.com/rs/xlog) and [xstats](https://github.com/rs/xstats) |
|
||||
| [cors](https://github.com/rs/cors) | [Olivier Poitrey](https://github.com/rs) | [Cross Origin Resource Sharing](http://www.w3.org/TR/cors/) (CORS) support |
|
||||
|
||||
## Licenses
|
||||
|
||||
All source code is licensed under the [MIT License](https://raw.github.com/rs/xhandler/master/LICENSE).
|
93
Godeps/_workspace/src/github.com/rs/xhandler/chain.go
generated
vendored
93
Godeps/_workspace/src/github.com/rs/xhandler/chain.go
generated
vendored
@ -1,93 +0,0 @@
|
||||
package xhandler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Chain is an helper to chain middleware handlers together for an easier
|
||||
// management.
|
||||
type Chain []func(next HandlerC) HandlerC
|
||||
|
||||
// UseC appends a context-aware handler to the middleware chain.
|
||||
func (c *Chain) UseC(f func(next HandlerC) HandlerC) {
|
||||
*c = append(*c, f)
|
||||
}
|
||||
|
||||
// Use appends a standard http.Handler to the middleware chain without
|
||||
// lossing track of the context when inserted between two context aware handlers.
|
||||
//
|
||||
// Caveat: the f function will be called on each request so you are better to put
|
||||
// any initialization sequence outside of this function.
|
||||
func (c *Chain) Use(f func(next http.Handler) http.Handler) {
|
||||
xf := func(next HandlerC) HandlerC {
|
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
n := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTPC(ctx, w, r)
|
||||
})
|
||||
f(n).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
*c = append(*c, xf)
|
||||
}
|
||||
|
||||
// Handler wraps the provided final handler with all the middleware appended to
|
||||
// the chain and return a new standard http.Handler instance.
|
||||
// The context.Background() context is injected automatically.
|
||||
func (c Chain) Handler(xh HandlerC) http.Handler {
|
||||
ctx := context.Background()
|
||||
return c.HandlerCtx(ctx, xh)
|
||||
}
|
||||
|
||||
// HandlerFC is an helper to provide a function (HandlerFuncC) to Handler().
|
||||
//
|
||||
// HandlerFC is equivalent to:
|
||||
// c.Handler(xhandler.HandlerFuncC(xhc))
|
||||
func (c Chain) HandlerFC(xhf HandlerFuncC) http.Handler {
|
||||
ctx := context.Background()
|
||||
return c.HandlerCtx(ctx, HandlerFuncC(xhf))
|
||||
}
|
||||
|
||||
// HandlerH is an helper to provide a standard http handler (http.HandlerFunc)
|
||||
// to Handler(). Your final handler won't have access the context though.
|
||||
func (c Chain) HandlerH(h http.Handler) http.Handler {
|
||||
ctx := context.Background()
|
||||
return c.HandlerCtx(ctx, HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
h.ServeHTTP(w, r)
|
||||
}))
|
||||
}
|
||||
|
||||
// HandlerF is an helper to provide a standard http handler function
|
||||
// (http.HandlerFunc) to Handler(). Your final handler won't have access
|
||||
// the context though.
|
||||
func (c Chain) HandlerF(hf http.HandlerFunc) http.Handler {
|
||||
ctx := context.Background()
|
||||
return c.HandlerCtx(ctx, HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
hf(w, r)
|
||||
}))
|
||||
}
|
||||
|
||||
// HandlerCtx wraps the provided final handler with all the middleware appended to
|
||||
// the chain and return a new standard http.Handler instance.
|
||||
func (c Chain) HandlerCtx(ctx context.Context, xh HandlerC) http.Handler {
|
||||
return New(ctx, c.HandlerC(xh))
|
||||
}
|
||||
|
||||
// HandlerC wraps the provided final handler with all the middleware appended to
|
||||
// the chain and returns a HandlerC instance.
|
||||
func (c Chain) HandlerC(xh HandlerC) HandlerC {
|
||||
for i := len(c) - 1; i >= 0; i-- {
|
||||
xh = c[i](xh)
|
||||
}
|
||||
return xh
|
||||
}
|
||||
|
||||
// HandlerCF wraps the provided final handler func with all the middleware appended to
|
||||
// the chain and returns a HandlerC instance.
|
||||
//
|
||||
// HandlerCF is equivalent to:
|
||||
// c.HandlerC(xhandler.HandlerFuncC(xhc))
|
||||
func (c Chain) HandlerCF(xhc HandlerFuncC) HandlerC {
|
||||
return c.HandlerC(HandlerFuncC(xhc))
|
||||
}
|
59
Godeps/_workspace/src/github.com/rs/xhandler/middleware.go
generated
vendored
59
Godeps/_workspace/src/github.com/rs/xhandler/middleware.go
generated
vendored
@ -1,59 +0,0 @@
|
||||
package xhandler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CloseHandler returns a Handler cancelling the context when the client
|
||||
// connection close unexpectedly.
|
||||
func CloseHandler(next HandlerC) HandlerC {
|
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// Cancel the context if the client closes the connection
|
||||
if wcn, ok := w.(http.CloseNotifier); ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
notify := wcn.CloseNotify()
|
||||
go func() {
|
||||
select {
|
||||
case <-notify:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
next.ServeHTTPC(ctx, w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// TimeoutHandler returns a Handler which adds a timeout to the context.
|
||||
//
|
||||
// Child handlers have the responsability to obey the context deadline and to return
|
||||
// an appropriate error (or not) response in case of timeout.
|
||||
func TimeoutHandler(timeout time.Duration) func(next HandlerC) HandlerC {
|
||||
return func(next HandlerC) HandlerC {
|
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
ctx, _ = context.WithTimeout(ctx, timeout)
|
||||
next.ServeHTTPC(ctx, w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If is a special handler that will skip insert the condNext handler only if a condition
|
||||
// applies at runtime.
|
||||
func If(cond func(ctx context.Context, w http.ResponseWriter, r *http.Request) bool, condNext func(next HandlerC) HandlerC) func(next HandlerC) HandlerC {
|
||||
return func(next HandlerC) HandlerC {
|
||||
return HandlerFuncC(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
if cond(ctx, w, r) {
|
||||
condNext(next).ServeHTTPC(ctx, w, r)
|
||||
} else {
|
||||
next.ServeHTTPC(ctx, w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
42
Godeps/_workspace/src/github.com/rs/xhandler/xhandler.go
generated
vendored
42
Godeps/_workspace/src/github.com/rs/xhandler/xhandler.go
generated
vendored
@ -1,42 +0,0 @@
|
||||
// Package xhandler provides a bridge between http.Handler and net/context.
|
||||
//
|
||||
// xhandler enforces net/context in your handlers without sacrificing
|
||||
// compatibility with existing http.Handlers nor imposing a specific router.
|
||||
//
|
||||
// Thanks to net/context deadline management, xhandler is able to enforce
|
||||
// a per request deadline and will cancel the context in when the client close
|
||||
// the connection unexpectedly.
|
||||
//
|
||||
// You may create net/context aware middlewares pretty much the same way as
|
||||
// you would do with http.Handler.
|
||||
package xhandler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// HandlerC is a net/context aware http.Handler
|
||||
type HandlerC interface {
|
||||
ServeHTTPC(context.Context, http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// HandlerFuncC type is an adapter to allow the use of ordinary functions
|
||||
// as a xhandler.Handler. If f is a function with the appropriate signature,
|
||||
// xhandler.HandlerFuncC(f) is a xhandler.Handler object that calls f.
|
||||
type HandlerFuncC func(context.Context, http.ResponseWriter, *http.Request)
|
||||
|
||||
// ServeHTTPC calls f(ctx, w, r).
|
||||
func (f HandlerFuncC) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
f(ctx, w, r)
|
||||
}
|
||||
|
||||
// New creates a conventional http.Handler injecting the provided root
|
||||
// context to sub handlers. This handler is used as a bridge between conventional
|
||||
// http.Handler and context aware handlers.
|
||||
func New(ctx context.Context, h HandlerC) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
h.ServeHTTPC(ctx, w, r)
|
||||
})
|
||||
}
|
@ -2,18 +2,22 @@ language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.0.3
|
||||
- 1.1.2
|
||||
- 1.2.2
|
||||
- 1.3.3
|
||||
- 1.4.2
|
||||
- 1.5.1
|
||||
- 1.4
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
before_script:
|
||||
- go get github.com/meatballhat/gfmxr/...
|
||||
|
||||
script:
|
||||
- go vet ./...
|
||||
- go test -v ./...
|
||||
- gfmxr -c $(grep -c 'package main' README.md) -s README.md
|
310
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/CHANGELOG.md
generated
vendored
Normal file
310
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,310 @@
|
||||
# Change Log
|
||||
|
||||
**ATTN**: This project uses [semantic versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.17.0] - 2016-05-09
|
||||
### Added
|
||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc`
|
||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool`
|
||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the
|
||||
commands in help output
|
||||
|
||||
### Changed
|
||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer
|
||||
quoted in help text output.
|
||||
- All flag types now include `(default: {value})` strings following usage when a
|
||||
default value can be (reasonably) detected.
|
||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent
|
||||
with non-slice flag types
|
||||
- Apps now exit with a code of 3 if an unknown subcommand is specified
|
||||
(previously they printed "No help topic for...", but still exited 0. This
|
||||
makes it easier to script around apps built using `cli` since they can trust
|
||||
that a 0 exit code indicated a successful execution.
|
||||
- cleanups based on [Go Report Card
|
||||
feedback](https://goreportcard.com/report/github.com/codegangsta/cli)
|
||||
|
||||
## [1.16.0] - 2016-05-02
|
||||
### Added
|
||||
- `Hidden` field on all flag struct types to omit from generated help text
|
||||
|
||||
### Changed
|
||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from
|
||||
generated help text via the `Hidden` field
|
||||
|
||||
### Fixed
|
||||
- handling of error values in `HandleAction` and `HandleExitCoder`
|
||||
|
||||
## [1.15.0] - 2016-04-30
|
||||
### Added
|
||||
- This file!
|
||||
- Support for placeholders in flag usage strings
|
||||
- `App.Metadata` map for arbitrary data/state management
|
||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after
|
||||
parsing.
|
||||
- Support for nested lookup of dot-delimited keys in structures loaded from
|
||||
YAML.
|
||||
|
||||
### Changed
|
||||
- The `App.Action` and `Command.Action` now prefer a return signature of
|
||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil
|
||||
`error` is returned, there may be two outcomes:
|
||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called
|
||||
automatically
|
||||
- Else the error is bubbled up and returned from `App.Run`
|
||||
- Specifying an `Action` with the legacy return signature of
|
||||
`func(*cli.Context)` will produce a deprecation message to stderr
|
||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit
|
||||
from `App.Run`
|
||||
- Specifying an `Action` func that has an invalid (input) signature will
|
||||
produce a non-zero exit from `App.Run`
|
||||
|
||||
### Deprecated
|
||||
- <a name="deprecated-cli-app-runandexitonerror"></a>
|
||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error
|
||||
that fulfills `cli.ExitCoder` to `cli.App.Run`.
|
||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for
|
||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return
|
||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
|
||||
|
||||
### Fixed
|
||||
- Added missing `*cli.Context.GlobalFloat64` method
|
||||
|
||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Codebeat badge
|
||||
- Support for categorization via `CategorizedHelp` and `Categories` on app.
|
||||
|
||||
### Changed
|
||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`.
|
||||
|
||||
### Fixed
|
||||
- Ensure version is not shown in help text when `HideVersion` set.
|
||||
|
||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- YAML file input support.
|
||||
- `NArg` method on context.
|
||||
|
||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Custom usage error handling.
|
||||
- Custom text support in `USAGE` section of help output.
|
||||
- Improved help messages for empty strings.
|
||||
- AppVeyor CI configuration.
|
||||
|
||||
### Changed
|
||||
- Removed `panic` from default help printer func.
|
||||
- De-duping and optimizations.
|
||||
|
||||
### Fixed
|
||||
- Correctly handle `Before`/`After` at command level when no subcommands.
|
||||
- Case of literal `-` argument causing flag reordering.
|
||||
- Environment variable hints on Windows.
|
||||
- Docs updates.
|
||||
|
||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25)
|
||||
### Changed
|
||||
- Use `path.Base` in `Name` and `HelpName`
|
||||
- Export `GetName` on flag types.
|
||||
|
||||
### Fixed
|
||||
- Flag parsing when skipping is enabled.
|
||||
- Test output cleanup.
|
||||
- Move completion check to account for empty input case.
|
||||
|
||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Destination scan support for flags.
|
||||
- Testing against `tip` in Travis CI config.
|
||||
|
||||
### Changed
|
||||
- Go version in Travis CI config.
|
||||
|
||||
### Fixed
|
||||
- Removed redundant tests.
|
||||
- Use correct example naming in tests.
|
||||
|
||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25)
|
||||
### Fixed
|
||||
- Remove unused var in bash completion.
|
||||
|
||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Coverage and reference logos in README.
|
||||
|
||||
### Fixed
|
||||
- Use specified values in help and version parsing.
|
||||
- Only display app version and help message once.
|
||||
|
||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- More tests for existing functionality.
|
||||
- `ArgsUsage` at app and command level for help text flexibility.
|
||||
|
||||
### Fixed
|
||||
- Honor `HideHelp` and `HideVersion` in `App.Run`.
|
||||
- Remove juvenile word from README.
|
||||
|
||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `FullName` on command with accompanying help output update.
|
||||
- Set default `$PROG` in bash completion.
|
||||
|
||||
### Changed
|
||||
- Docs formatting.
|
||||
|
||||
### Fixed
|
||||
- Removed self-referential imports in tests.
|
||||
|
||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for `Copyright` at app level.
|
||||
- `Parent` func at context level to walk up context lineage.
|
||||
|
||||
### Fixed
|
||||
- Global flag processing at top level.
|
||||
|
||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Aggregate errors from `Before`/`After` funcs.
|
||||
- Doc comments on flag structs.
|
||||
- Include non-global flags when checking version and help.
|
||||
- Travis CI config updates.
|
||||
|
||||
### Fixed
|
||||
- Ensure slice type flags have non-nil values.
|
||||
- Collect global flags from the full command hierarchy.
|
||||
- Docs prose.
|
||||
|
||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25)
|
||||
### Changed
|
||||
- `HelpPrinter` signature includes output writer.
|
||||
|
||||
### Fixed
|
||||
- Specify go 1.1+ in docs.
|
||||
- Set `Writer` when running command as app.
|
||||
|
||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Multiple author support.
|
||||
- `NumFlags` at context level.
|
||||
- `Aliases` at command level.
|
||||
|
||||
### Deprecated
|
||||
- `ShortName` at command level.
|
||||
|
||||
### Fixed
|
||||
- Subcommand help output.
|
||||
- Backward compatible support for deprecated `Author` and `Email` fields.
|
||||
- Docs regarding `Names`/`Aliases`.
|
||||
|
||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `After` hook func support at app and command level.
|
||||
|
||||
### Fixed
|
||||
- Use parsed context when running command as subcommand.
|
||||
- Docs prose.
|
||||
|
||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for hiding `-h / --help` flags, but not `help` subcommand.
|
||||
- Stop flag parsing after `--`.
|
||||
|
||||
### Fixed
|
||||
- Help text for generic flags to specify single value.
|
||||
- Use double quotes in output for defaults.
|
||||
- Use `ParseInt` instead of `ParseUint` for int environment var values.
|
||||
- Use `0` as base when parsing int environment var values.
|
||||
|
||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Support for environment variable lookup "cascade".
|
||||
- Support for `Stdout` on app for output redirection.
|
||||
|
||||
### Fixed
|
||||
- Print command help instead of app help in `ShowCommandHelp`.
|
||||
|
||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- Docs and example code updates.
|
||||
|
||||
### Changed
|
||||
- Default `-v / --version` flag made optional.
|
||||
|
||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25)
|
||||
### Added
|
||||
- `FlagNames` at context level.
|
||||
- Exposed `VersionPrinter` var for more control over version output.
|
||||
- Zsh completion hook.
|
||||
- `AUTHOR` section in default app help template.
|
||||
- Contribution guidelines.
|
||||
- `DurationFlag` type.
|
||||
|
||||
## [1.2.0] - 2014-08-02
|
||||
### Added
|
||||
- Support for environment variable defaults on flags plus tests.
|
||||
|
||||
## [1.1.0] - 2014-07-15
|
||||
### Added
|
||||
- Bash completion.
|
||||
- Optional hiding of built-in help command.
|
||||
- Optional skipping of flag parsing at command level.
|
||||
- `Author`, `Email`, and `Compiled` metadata on app.
|
||||
- `Before` hook func support at app and command level.
|
||||
- `CommandNotFound` func support at app level.
|
||||
- Command reference available on context.
|
||||
- `GenericFlag` type.
|
||||
- `Float64Flag` type.
|
||||
- `BoolTFlag` type.
|
||||
- `IsSet` flag helper on context.
|
||||
- More flag lookup funcs at context level.
|
||||
- More tests & docs.
|
||||
|
||||
### Changed
|
||||
- Help template updates to account for presence/absence of flags.
|
||||
- Separated subcommand help template.
|
||||
- Exposed `HelpPrinter` var for more control over help output.
|
||||
|
||||
## [1.0.0] - 2013-11-01
|
||||
### Added
|
||||
- `help` flag in default app flag set and each command flag set.
|
||||
- Custom handling of argument parsing errors.
|
||||
- Command lookup by name at app level.
|
||||
- `StringSliceFlag` type and supporting `StringSlice` type.
|
||||
- `IntSliceFlag` type and supporting `IntSlice` type.
|
||||
- Slice type flag lookups by name at context level.
|
||||
- Export of app and command help functions.
|
||||
- More tests & docs.
|
||||
|
||||
## 0.1.0 - 2013-07-22
|
||||
### Added
|
||||
- Initial implementation.
|
||||
|
||||
[Unreleased]: https://github.com/codegangsta/cli/compare/v1.17.0...HEAD
|
||||
[1.17.0]: https://github.com/codegangsta/cli/compare/v1.16.0...v1.17.0
|
||||
[1.16.0]: https://github.com/codegangsta/cli/compare/v1.15.0...v1.16.0
|
||||
[1.15.0]: https://github.com/codegangsta/cli/compare/v1.14.0...v1.15.0
|
||||
[1.14.0]: https://github.com/codegangsta/cli/compare/v1.13.0...v1.14.0
|
||||
[1.13.0]: https://github.com/codegangsta/cli/compare/v1.12.0...v1.13.0
|
||||
[1.12.0]: https://github.com/codegangsta/cli/compare/v1.11.1...v1.12.0
|
||||
[1.11.1]: https://github.com/codegangsta/cli/compare/v1.11.0...v1.11.1
|
||||
[1.11.0]: https://github.com/codegangsta/cli/compare/v1.10.2...v1.11.0
|
||||
[1.10.2]: https://github.com/codegangsta/cli/compare/v1.10.1...v1.10.2
|
||||
[1.10.1]: https://github.com/codegangsta/cli/compare/v1.10.0...v1.10.1
|
||||
[1.10.0]: https://github.com/codegangsta/cli/compare/v1.9.0...v1.10.0
|
||||
[1.9.0]: https://github.com/codegangsta/cli/compare/v1.8.0...v1.9.0
|
||||
[1.8.0]: https://github.com/codegangsta/cli/compare/v1.7.1...v1.8.0
|
||||
[1.7.1]: https://github.com/codegangsta/cli/compare/v1.7.0...v1.7.1
|
||||
[1.7.0]: https://github.com/codegangsta/cli/compare/v1.6.0...v1.7.0
|
||||
[1.6.0]: https://github.com/codegangsta/cli/compare/v1.5.0...v1.6.0
|
||||
[1.5.0]: https://github.com/codegangsta/cli/compare/v1.4.1...v1.5.0
|
||||
[1.4.1]: https://github.com/codegangsta/cli/compare/v1.4.0...v1.4.1
|
||||
[1.4.0]: https://github.com/codegangsta/cli/compare/v1.3.1...v1.4.0
|
||||
[1.3.1]: https://github.com/codegangsta/cli/compare/v1.3.0...v1.3.1
|
||||
[1.3.0]: https://github.com/codegangsta/cli/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/codegangsta/cli/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/codegangsta/cli/compare/v1.0.0...v1.1.0
|
||||
[1.0.0]: https://github.com/codegangsta/cli/compare/v0.1.0...v1.0.0
|
@ -1,22 +1,24 @@
|
||||
[](http://gocover.io/github.com/codegangsta/cli)
|
||||
[](https://travis-ci.org/codegangsta/cli)
|
||||
[](https://godoc.org/github.com/codegangsta/cli)
|
||||
[](https://codebeat.co/projects/github-com-codegangsta-cli)
|
||||
[](https://goreportcard.com/report/codegangsta/cli)
|
||||
|
||||
# cli.go
|
||||
# cli
|
||||
|
||||
`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
|
||||
cli is a simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way.
|
||||
|
||||
## Overview
|
||||
|
||||
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app.
|
||||
|
||||
**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive!
|
||||
**This is where cli comes into play.** cli makes command line programming fun, organized, and expressive!
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html).
|
||||
|
||||
To install `cli.go`, simply run:
|
||||
To install cli, simply run:
|
||||
```
|
||||
$ go get github.com/codegangsta/cli
|
||||
```
|
||||
@ -28,7 +30,7 @@ export PATH=$PATH:$GOPATH/bin
|
||||
|
||||
## Getting Started
|
||||
|
||||
One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`.
|
||||
One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`.
|
||||
|
||||
``` go
|
||||
package main
|
||||
@ -45,11 +47,16 @@ func main() {
|
||||
|
||||
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation:
|
||||
|
||||
<!-- {
|
||||
"output": "boom! I say!"
|
||||
} -->
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
@ -57,10 +64,11 @@ func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "boom"
|
||||
app.Usage = "make an explosive entrance"
|
||||
app.Action = func(c *cli.Context) {
|
||||
println("boom! I say!")
|
||||
app.Action = func(c *cli.Context) error {
|
||||
fmt.Println("boom! I say!")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
```
|
||||
@ -73,11 +81,16 @@ Being a programmer can be a lonely job. Thankfully by the power of automation th
|
||||
|
||||
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it:
|
||||
|
||||
<!-- {
|
||||
"output": "Hello friend!"
|
||||
} -->
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
@ -85,8 +98,9 @@ func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "greet"
|
||||
app.Usage = "fight the loneliness!"
|
||||
app.Action = func(c *cli.Context) {
|
||||
println("Hello friend!")
|
||||
app.Action = func(c *cli.Context) error {
|
||||
fmt.Println("Hello friend!")
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
@ -106,7 +120,7 @@ $ greet
|
||||
Hello friend!
|
||||
```
|
||||
|
||||
`cli.go` also generates neat help text:
|
||||
cli also generates neat help text:
|
||||
|
||||
```
|
||||
$ greet help
|
||||
@ -132,8 +146,9 @@ You can lookup arguments by calling the `Args` function on `cli.Context`.
|
||||
|
||||
``` go
|
||||
...
|
||||
app.Action = func(c *cli.Context) {
|
||||
println("Hello", c.Args()[0])
|
||||
app.Action = func(c *cli.Context) error {
|
||||
fmt.Println("Hello", c.Args()[0])
|
||||
return nil
|
||||
}
|
||||
...
|
||||
```
|
||||
@ -151,16 +166,17 @@ app.Flags = []cli.Flag {
|
||||
Usage: "language for the greeting",
|
||||
},
|
||||
}
|
||||
app.Action = func(c *cli.Context) {
|
||||
app.Action = func(c *cli.Context) error {
|
||||
name := "someone"
|
||||
if len(c.Args()) > 0 {
|
||||
if c.NArg() > 0 {
|
||||
name = c.Args()[0]
|
||||
}
|
||||
if c.String("lang") == "spanish" {
|
||||
println("Hola", name)
|
||||
fmt.Println("Hola", name)
|
||||
} else {
|
||||
println("Hello", name)
|
||||
fmt.Println("Hello", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
...
|
||||
```
|
||||
@ -178,22 +194,45 @@ app.Flags = []cli.Flag {
|
||||
Destination: &language,
|
||||
},
|
||||
}
|
||||
app.Action = func(c *cli.Context) {
|
||||
app.Action = func(c *cli.Context) error {
|
||||
name := "someone"
|
||||
if len(c.Args()) > 0 {
|
||||
if c.NArg() > 0 {
|
||||
name = c.Args()[0]
|
||||
}
|
||||
if language == "spanish" {
|
||||
println("Hola", name)
|
||||
fmt.Println("Hola", name)
|
||||
} else {
|
||||
println("Hello", name)
|
||||
fmt.Println("Hello", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
See full list of flags at http://godoc.org/github.com/codegangsta/cli
|
||||
|
||||
#### Placeholder Values
|
||||
|
||||
Sometimes it's useful to specify a flag's value within the usage string itself. Such placeholders are
|
||||
indicated with back quotes.
|
||||
|
||||
For example this:
|
||||
|
||||
```go
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "Load configuration from `FILE`",
|
||||
}
|
||||
```
|
||||
|
||||
Will result in help output like:
|
||||
|
||||
```
|
||||
--config FILE, -c FILE Load configuration from FILE
|
||||
```
|
||||
|
||||
Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is.
|
||||
|
||||
#### Alternate Names
|
||||
|
||||
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g.
|
||||
@ -238,6 +277,49 @@ app.Flags = []cli.Flag {
|
||||
}
|
||||
```
|
||||
|
||||
#### Values from alternate input sources (YAML and others)
|
||||
|
||||
There is a separate package altsrc that adds support for getting flag values from other input sources like YAML.
|
||||
|
||||
In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below:
|
||||
|
||||
``` go
|
||||
altsrc.NewIntFlag(cli.IntFlag{Name: "test"})
|
||||
```
|
||||
|
||||
Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below.
|
||||
|
||||
``` go
|
||||
command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
```
|
||||
|
||||
The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context.
|
||||
It will then use that file name to initialize the yaml input source for any flags that are defined on that command.
|
||||
As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work.
|
||||
|
||||
Currently only YAML files are supported but developers can add support for other input sources by implementing the
|
||||
altsrc.InputSourceContext for their given sources.
|
||||
|
||||
Here is a more complete sample of a command using YAML support:
|
||||
|
||||
``` go
|
||||
command := &cli.Command{
|
||||
Name: "test-cmd",
|
||||
Aliases: []string{"tc"},
|
||||
Usage: "this is for testing",
|
||||
Description: "testing",
|
||||
Action: func(c *cli.Context) error {
|
||||
// Action to run
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
NewIntFlag(cli.IntFlag{Name: "test"}),
|
||||
cli.StringFlag{Name: "load"}},
|
||||
}
|
||||
command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load"))
|
||||
err := command.Run(c)
|
||||
```
|
||||
|
||||
### Subcommands
|
||||
|
||||
Subcommands can be defined for a more git-like command line app.
|
||||
@ -249,16 +331,18 @@ app.Commands = []cli.Command{
|
||||
Name: "add",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "add a task to the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("added task: ", c.Args().First())
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("added task: ", c.Args().First())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "complete",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("completed task: ", c.Args().First())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -269,15 +353,17 @@ app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Usage: "add a new template",
|
||||
Action: func(c *cli.Context) {
|
||||
println("new task template: ", c.Args().First())
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("new task template: ", c.Args().First())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove an existing template",
|
||||
Action: func(c *cli.Context) {
|
||||
println("removed task template: ", c.Args().First())
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("removed task template: ", c.Args().First())
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -286,6 +372,80 @@ app.Commands = []cli.Command{
|
||||
...
|
||||
```
|
||||
|
||||
### Subcommands categories
|
||||
|
||||
For additional organization in apps that have many subcommands, you can
|
||||
associate a category for each command to group them together in the help
|
||||
output.
|
||||
|
||||
E.g.
|
||||
|
||||
```go
|
||||
...
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "noop",
|
||||
},
|
||||
{
|
||||
Name: "add",
|
||||
Category: "template",
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Category: "template",
|
||||
},
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Will include:
|
||||
|
||||
```
|
||||
...
|
||||
COMMANDS:
|
||||
noop
|
||||
|
||||
Template actions:
|
||||
add
|
||||
remove
|
||||
...
|
||||
```
|
||||
|
||||
### Exit code
|
||||
|
||||
Calling `App.Run` will not automatically call `os.Exit`, which means that by
|
||||
default the exit code will "fall through" to being `0`. An explicit exit code
|
||||
may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
|
||||
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolTFlag{
|
||||
Name: "ginger-crouton",
|
||||
Usage: "is it in the soup?",
|
||||
},
|
||||
}
|
||||
app.Action = func(ctx *cli.Context) error {
|
||||
if !ctx.Bool("ginger-crouton") {
|
||||
return cli.NewExitError("it is not in the soup", 86)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
}
|
||||
```
|
||||
|
||||
### Bash Completion
|
||||
|
||||
You can enable completion commands by setting the `EnableBashCompletion`
|
||||
@ -303,12 +463,13 @@ app.Commands = []cli.Command{
|
||||
Name: "complete",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "complete a task on the list",
|
||||
Action: func(c *cli.Context) {
|
||||
println("completed task: ", c.Args().First())
|
||||
Action: func(c *cli.Context) error {
|
||||
fmt.Println("completed task: ", c.Args().First())
|
||||
return nil
|
||||
},
|
||||
BashComplete: func(c *cli.Context) {
|
||||
// This will complete if no args are passed
|
||||
if len(c.Args()) > 0 {
|
||||
if c.NArg() > 0 {
|
||||
return
|
||||
}
|
||||
for _, t := range tasks {
|
||||
@ -343,6 +504,72 @@ Alternatively, you can just document that users should source the generic
|
||||
`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set
|
||||
to the name of their program (as above).
|
||||
|
||||
### Generated Help Text Customization
|
||||
|
||||
All of the help text generation may be customized, and at multiple levels. The
|
||||
templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and
|
||||
`SubcommandHelpTemplate` which may be reassigned or augmented, and full override
|
||||
is possible by assigning a compatible func to the `cli.HelpPrinter` variable,
|
||||
e.g.:
|
||||
|
||||
<!-- {
|
||||
"output": "Ha HA. I pwnd the help!!1"
|
||||
} -->
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// EXAMPLE: Append to an existing template
|
||||
cli.AppHelpTemplate = fmt.Sprintf(`%s
|
||||
|
||||
WEBSITE: http://awesometown.example.com
|
||||
|
||||
SUPPORT: support@awesometown.example.com
|
||||
|
||||
`, cli.AppHelpTemplate)
|
||||
|
||||
// EXAMPLE: Override a template
|
||||
cli.AppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
USAGE:
|
||||
{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command
|
||||
[command options]{{end}} {{if
|
||||
.ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
{{if len .Authors}}
|
||||
AUTHOR(S):
|
||||
{{range .Authors}}{{ . }}{{end}}
|
||||
{{end}}{{if .Commands}}
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"
|
||||
}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
|
||||
GLOBAL OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright }}
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}
|
||||
{{end}}{{if .Version}}
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// EXAMPLE: Replace the `HelpPrinter` func
|
||||
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
|
||||
fmt.Println("Ha HA. I pwnd the help!!1")
|
||||
}
|
||||
|
||||
cli.NewApp().Run(os.Args)
|
||||
}
|
||||
```
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch.
|
@ -5,10 +5,27 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
changeLogURL = "https://github.com/codegangsta/cli/blob/master/CHANGELOG.md"
|
||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
|
||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
|
||||
|
||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
|
||||
|
||||
errNonFuncAction = NewExitError("ERROR invalid Action type. "+
|
||||
fmt.Sprintf("Must be a func of type `cli.ActionFunc`. %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
errInvalidActionSignature = NewExitError("ERROR invalid Action signature. "+
|
||||
fmt.Sprintf("Must be `cli.ActionFunc`. %s", contactSysadmin)+
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
|
||||
)
|
||||
|
||||
// App is the main structure of a cli application. It is recommended that
|
||||
// an app be created with the cli.NewApp() function
|
||||
type App struct {
|
||||
@ -32,24 +49,27 @@ type App struct {
|
||||
EnableBashCompletion bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide built-in version flag
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
||||
HideVersion bool
|
||||
// Populate on app startup, only gettable through method Categories()
|
||||
categories CommandCategories
|
||||
// An action to execute when the bash-completion flag is set
|
||||
BashComplete func(context *Context)
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no subcommands are run
|
||||
Before func(context *Context) error
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After func(context *Context) error
|
||||
After AfterFunc
|
||||
// The action to execute when no subcommands are specified
|
||||
Action func(context *Context)
|
||||
Action interface{}
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
||||
// of deprecation period has passed, maybe?
|
||||
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound func(context *Context, command string)
|
||||
// Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages.
|
||||
// This function is able to replace the original error messages.
|
||||
// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
|
||||
OnUsageError func(context *Context, err error, isSubcommand bool) error
|
||||
CommandNotFound CommandNotFoundFunc
|
||||
// Execute this function if an usage error occurs
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// Compilation date
|
||||
Compiled time.Time
|
||||
// List of all authors who contributed
|
||||
@ -62,6 +82,12 @@ type App struct {
|
||||
Email string
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer
|
||||
// Other custom info
|
||||
Metadata map[string]interface{}
|
||||
|
||||
didSetup bool
|
||||
}
|
||||
|
||||
// Tries to find out when this binary was compiled.
|
||||
@ -74,11 +100,12 @@ func compileTime() time.Time {
|
||||
return info.ModTime()
|
||||
}
|
||||
|
||||
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
|
||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
||||
// Usage, Version and Action.
|
||||
func NewApp() *App {
|
||||
return &App{
|
||||
Name: path.Base(os.Args[0]),
|
||||
HelpName: path.Base(os.Args[0]),
|
||||
Name: filepath.Base(os.Args[0]),
|
||||
HelpName: filepath.Base(os.Args[0]),
|
||||
Usage: "A new cli application",
|
||||
UsageText: "",
|
||||
Version: "0.0.0",
|
||||
@ -89,8 +116,16 @@ func NewApp() *App {
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) (err error) {
|
||||
// Setup runs initialization code to ensure all data structures are ready for
|
||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
||||
// will return early if setup has already happened.
|
||||
func (a *App) Setup() {
|
||||
if a.didSetup {
|
||||
return
|
||||
}
|
||||
|
||||
a.didSetup = true
|
||||
|
||||
if a.Author != "" || a.Email != "" {
|
||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email})
|
||||
}
|
||||
@ -104,6 +139,12 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
}
|
||||
a.Commands = newCmds
|
||||
|
||||
a.categories = CommandCategories{}
|
||||
for _, command := range a.Commands {
|
||||
a.categories = a.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
sort.Sort(a.categories)
|
||||
|
||||
// append help to commands
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
|
||||
a.Commands = append(a.Commands, helpCommand)
|
||||
@ -120,6 +161,12 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
if !a.HideVersion {
|
||||
a.appendFlag(VersionFlag)
|
||||
}
|
||||
}
|
||||
|
||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
||||
// to the proper flag/args combination
|
||||
func (a *App) Run(arguments []string) (err error) {
|
||||
a.Setup()
|
||||
|
||||
// parse flags
|
||||
set := flagSet(a.Name, a.Flags)
|
||||
@ -140,12 +187,12 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err := a.OnUsageError(context, err, false)
|
||||
return err
|
||||
} else {
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowAppHelp(context)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowAppHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if !a.HideHelp && checkHelp(context) {
|
||||
@ -171,10 +218,12 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
err = a.Before(context)
|
||||
if err != nil {
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", err)
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
|
||||
ShowAppHelp(context)
|
||||
HandleExitCoder(beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -189,19 +238,25 @@ func (a *App) Run(arguments []string) (err error) {
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
a.Action(context)
|
||||
return nil
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Another entry point to the cli app, takes care of passing arguments and error handling
|
||||
// DEPRECATED: Another entry point to the cli app, takes care of passing arguments and error handling
|
||||
func (a *App) RunAndExitOnError() {
|
||||
fmt.Fprintf(a.errWriter(),
|
||||
"DEPRECATED cli.App.RunAndExitOnError. %s See %s\n",
|
||||
contactSysadmin, runAndExitOnErrorDeprecationURL)
|
||||
if err := a.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
fmt.Fprintln(a.errWriter(), err)
|
||||
OsExiter(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
||||
// generate command-specific flags
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
// append help to commands
|
||||
if len(a.Commands) > 0 {
|
||||
@ -252,12 +307,12 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
if err != nil {
|
||||
if a.OnUsageError != nil {
|
||||
err = a.OnUsageError(context, err, true)
|
||||
return err
|
||||
} else {
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowSubcommandHelp(context)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.")
|
||||
ShowSubcommandHelp(context)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(a.Commands) > 0 {
|
||||
@ -274,6 +329,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
defer func() {
|
||||
afterErr := a.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -284,8 +340,10 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
}
|
||||
|
||||
if a.Before != nil {
|
||||
err := a.Before(context)
|
||||
if err != nil {
|
||||
beforeErr := a.Before(context)
|
||||
if beforeErr != nil {
|
||||
HandleExitCoder(beforeErr)
|
||||
err = beforeErr
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -300,12 +358,13 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
|
||||
}
|
||||
|
||||
// Run default Action
|
||||
a.Action(context)
|
||||
err = HandleAction(a.Action, context)
|
||||
|
||||
return nil
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Returns the named command on App. Returns nil if the command does not exist
|
||||
// Command returns the named command on App. Returns nil if the command does not exist
|
||||
func (a *App) Command(name string) *Command {
|
||||
for _, c := range a.Commands {
|
||||
if c.HasName(name) {
|
||||
@ -316,6 +375,46 @@ func (a *App) Command(name string) *Command {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Categories returns a slice containing all the categories with the commands they contain
|
||||
func (a *App) Categories() CommandCategories {
|
||||
return a.categories
|
||||
}
|
||||
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
||||
// Hidden=false
|
||||
func (a *App) VisibleCategories() []*CommandCategory {
|
||||
ret := []*CommandCategory{}
|
||||
for _, category := range a.categories {
|
||||
if visible := func() *CommandCategory {
|
||||
for _, command := range category.Commands {
|
||||
if !command.Hidden {
|
||||
return category
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(); visible != nil {
|
||||
ret = append(ret, visible)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (a *App) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range a.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (a *App) VisibleFlags() []Flag {
|
||||
return visibleFlags(a.Flags)
|
||||
}
|
||||
|
||||
func (a *App) hasFlag(flag Flag) bool {
|
||||
for _, f := range a.Flags {
|
||||
if flag == f {
|
||||
@ -326,6 +425,16 @@ func (a *App) hasFlag(flag Flag) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) errWriter() io.Writer {
|
||||
|
||||
// When the app ErrWriter is nil use the package level one.
|
||||
if a.ErrWriter == nil {
|
||||
return ErrWriter
|
||||
}
|
||||
|
||||
return a.ErrWriter
|
||||
}
|
||||
|
||||
func (a *App) appendFlag(flag Flag) {
|
||||
if !a.hasFlag(flag) {
|
||||
a.Flags = append(a.Flags, flag)
|
||||
@ -347,3 +456,43 @@ func (a Author) String() string {
|
||||
|
||||
return fmt.Sprintf("%v %v", a.Name, e)
|
||||
}
|
||||
|
||||
// HandleAction uses ✧✧✧reflection✧✧✧ to figure out if the given Action is an
|
||||
// ActionFunc, a func with the legacy signature for Action, or some other
|
||||
// invalid thing. If it's an ActionFunc or a func with the legacy signature for
|
||||
// Action, the func is run!
|
||||
func HandleAction(action interface{}, context *Context) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
switch r.(type) {
|
||||
case error:
|
||||
err = r.(error)
|
||||
default:
|
||||
err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if reflect.TypeOf(action).Kind() != reflect.Func {
|
||||
return errNonFuncAction
|
||||
}
|
||||
|
||||
vals := reflect.ValueOf(action).Call([]reflect.Value{reflect.ValueOf(context)})
|
||||
|
||||
if len(vals) == 0 {
|
||||
fmt.Fprintf(ErrWriter,
|
||||
"DEPRECATED Action signature. Must be `cli.ActionFunc`. %s See %s\n",
|
||||
contactSysadmin, appActionDeprecationURL)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(vals) > 1 {
|
||||
return errInvalidActionSignature
|
||||
}
|
||||
|
||||
if retErr, ok := vals[0].Interface().(error); vals[0].IsValid() && ok {
|
||||
return retErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
44
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/category.go
generated
vendored
Normal file
44
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/category.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package cli
|
||||
|
||||
// CommandCategories is a slice of *CommandCategory.
|
||||
type CommandCategories []*CommandCategory
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory struct {
|
||||
Name string
|
||||
Commands Commands
|
||||
}
|
||||
|
||||
func (c CommandCategories) Less(i, j int) bool {
|
||||
return c[i].Name < c[j].Name
|
||||
}
|
||||
|
||||
func (c CommandCategories) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c CommandCategories) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
// AddCommand adds a command to a category.
|
||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories {
|
||||
for _, commandCategory := range c {
|
||||
if commandCategory.Name == category {
|
||||
commandCategory.Commands = append(commandCategory.Commands, command)
|
||||
return c
|
||||
}
|
||||
}
|
||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}})
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (c *CommandCategory) VisibleCommands() []Command {
|
||||
ret := []Command{}
|
||||
for _, command := range c.Commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
@ -10,31 +10,10 @@
|
||||
// app := cli.NewApp()
|
||||
// app.Name = "greet"
|
||||
// app.Usage = "say a greeting"
|
||||
// app.Action = func(c *cli.Context) {
|
||||
// app.Action = func(c *cli.Context) error {
|
||||
// println("Greetings")
|
||||
// }
|
||||
//
|
||||
// app.Run(os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type MultiError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func NewMultiError(err ...error) MultiError {
|
||||
return MultiError{Errors: err}
|
||||
}
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
errs := make([]string, len(m.Errors))
|
||||
for i, err := range m.Errors {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
@ -3,6 +3,7 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -22,35 +23,40 @@ type Command struct {
|
||||
Description string
|
||||
// A short description of the arguments of this command
|
||||
ArgsUsage string
|
||||
// The category the command is part of
|
||||
Category string
|
||||
// The function to call when checking for bash command completions
|
||||
BashComplete func(context *Context)
|
||||
BashComplete BashCompleteFunc
|
||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no sub-subcommands are run
|
||||
Before func(context *Context) error
|
||||
Before BeforeFunc
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After func(context *Context) error
|
||||
After AfterFunc
|
||||
// The function to call when this command is invoked
|
||||
Action func(context *Context)
|
||||
// Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages.
|
||||
// This function is able to replace the original error messages.
|
||||
// If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted.
|
||||
OnUsageError func(context *Context, err error) error
|
||||
Action interface{}
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
||||
// of deprecation period has passed, maybe?
|
||||
|
||||
// Execute this function if a usage error occurs.
|
||||
OnUsageError OnUsageErrorFunc
|
||||
// List of child commands
|
||||
Subcommands []Command
|
||||
Subcommands Commands
|
||||
// List of flags to parse
|
||||
Flags []Flag
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool
|
||||
// Boolean to hide built-in help command
|
||||
HideHelp bool
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool
|
||||
|
||||
// Full name of command for help, defaults to full command name, including parent commands.
|
||||
HelpName string
|
||||
commandNamePath []string
|
||||
}
|
||||
|
||||
// Returns the full name of the command.
|
||||
// FullName returns the full name of the command.
|
||||
// For subcommands this ensures that parent commands are part of the command path
|
||||
func (c Command) FullName() string {
|
||||
if c.commandNamePath == nil {
|
||||
@ -59,7 +65,10 @@ func (c Command) FullName() string {
|
||||
return strings.Join(c.commandNamePath, " ")
|
||||
}
|
||||
|
||||
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
// Commands is a slice of Command
|
||||
type Commands []Command
|
||||
|
||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||
func (c Command) Run(ctx *Context) (err error) {
|
||||
if len(c.Subcommands) > 0 {
|
||||
return c.startApp(ctx)
|
||||
@ -120,14 +129,14 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
|
||||
if err != nil {
|
||||
if c.OnUsageError != nil {
|
||||
err := c.OnUsageError(ctx, err)
|
||||
return err
|
||||
} else {
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
err := c.OnUsageError(ctx, err, false)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.")
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
nerr := normalizeFlags(c.Flags, set)
|
||||
@ -137,6 +146,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
return nerr
|
||||
}
|
||||
|
||||
context := NewContext(ctx.App, set, ctx)
|
||||
|
||||
if checkCommandCompletions(context, c.Name) {
|
||||
@ -151,6 +161,7 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
defer func() {
|
||||
afterErr := c.After(context)
|
||||
if afterErr != nil {
|
||||
HandleExitCoder(err)
|
||||
if err != nil {
|
||||
err = NewMultiError(err, afterErr)
|
||||
} else {
|
||||
@ -161,20 +172,26 @@ func (c Command) Run(ctx *Context) (err error) {
|
||||
}
|
||||
|
||||
if c.Before != nil {
|
||||
err := c.Before(context)
|
||||
err = c.Before(context)
|
||||
if err != nil {
|
||||
fmt.Fprintln(ctx.App.Writer, err)
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
ShowCommandHelp(ctx, c.Name)
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
context.Command = c
|
||||
c.Action(context)
|
||||
return nil
|
||||
err = HandleAction(c.Action, context)
|
||||
|
||||
if err != nil {
|
||||
HandleExitCoder(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (c Command) Names() []string {
|
||||
names := []string{c.Name}
|
||||
|
||||
@ -185,7 +202,7 @@ func (c Command) Names() []string {
|
||||
return append(names, c.Aliases...)
|
||||
}
|
||||
|
||||
// Returns true if Command.Name or Command.ShortName matches given name
|
||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
||||
func (c Command) HasName(name string) bool {
|
||||
for _, n := range c.Names() {
|
||||
if n == name {
|
||||
@ -197,7 +214,7 @@ func (c Command) HasName(name string) bool {
|
||||
|
||||
func (c Command) startApp(ctx *Context) error {
|
||||
app := NewApp()
|
||||
|
||||
app.Metadata = ctx.App.Metadata
|
||||
// set the name and usage
|
||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
||||
if c.HelpName == "" {
|
||||
@ -227,6 +244,13 @@ func (c Command) startApp(ctx *Context) error {
|
||||
app.Email = ctx.App.Email
|
||||
app.Writer = ctx.App.Writer
|
||||
|
||||
app.categories = CommandCategories{}
|
||||
for _, command := range c.Subcommands {
|
||||
app.categories = app.categories.AddCommand(command.Category, command)
|
||||
}
|
||||
|
||||
sort.Sort(app.categories)
|
||||
|
||||
// bash completion
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
||||
if c.BashComplete != nil {
|
||||
@ -248,3 +272,8 @@ func (c Command) startApp(ctx *Context) error {
|
||||
|
||||
return app.RunAsSubcommand(ctx)
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (c Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(c.Flags)
|
||||
}
|
@ -21,57 +21,62 @@ type Context struct {
|
||||
parentContext *Context
|
||||
}
|
||||
|
||||
// Creates a new context. For use in when invoking an App or Command action.
|
||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context {
|
||||
return &Context{App: app, flagSet: set, parentContext: parentCtx}
|
||||
}
|
||||
|
||||
// Looks up the value of a local int flag, returns 0 if no int flag exists
|
||||
// Int looks up the value of a local int flag, returns 0 if no int flag exists
|
||||
func (c *Context) Int(name string) int {
|
||||
return lookupInt(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
|
||||
// Duration looks up the value of a local time.Duration flag, returns 0 if no
|
||||
// time.Duration flag exists
|
||||
func (c *Context) Duration(name string) time.Duration {
|
||||
return lookupDuration(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
|
||||
// Float64 looks up the value of a local float64 flag, returns 0 if no float64
|
||||
// flag exists
|
||||
func (c *Context) Float64(name string) float64 {
|
||||
return lookupFloat64(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local bool flag, returns false if no bool flag exists
|
||||
// Bool looks up the value of a local bool flag, returns false if no bool flag exists
|
||||
func (c *Context) Bool(name string) bool {
|
||||
return lookupBool(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local boolT flag, returns false if no bool flag exists
|
||||
// BoolT looks up the value of a local boolT flag, returns false if no bool flag exists
|
||||
func (c *Context) BoolT(name string) bool {
|
||||
return lookupBoolT(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local string flag, returns "" if no string flag exists
|
||||
// String looks up the value of a local string flag, returns "" if no string flag exists
|
||||
func (c *Context) String(name string) string {
|
||||
return lookupString(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
|
||||
// StringSlice looks up the value of a local string slice flag, returns nil if no
|
||||
// string slice flag exists
|
||||
func (c *Context) StringSlice(name string) []string {
|
||||
return lookupStringSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
|
||||
// IntSlice looks up the value of a local int slice flag, returns nil if no int
|
||||
// slice flag exists
|
||||
func (c *Context) IntSlice(name string) []int {
|
||||
return lookupIntSlice(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a local generic flag, returns nil if no generic flag exists
|
||||
// Generic looks up the value of a local generic flag, returns nil if no generic
|
||||
// flag exists
|
||||
func (c *Context) Generic(name string) interface{} {
|
||||
return lookupGeneric(name, c.flagSet)
|
||||
}
|
||||
|
||||
// Looks up the value of a global int flag, returns 0 if no int flag exists
|
||||
// GlobalInt looks up the value of a global int flag, returns 0 if no int flag exists
|
||||
func (c *Context) GlobalInt(name string) int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupInt(name, fs)
|
||||
@ -79,7 +84,17 @@ func (c *Context) GlobalInt(name string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
|
||||
// GlobalFloat64 looks up the value of a global float64 flag, returns float64(0)
|
||||
// if no float64 flag exists
|
||||
func (c *Context) GlobalFloat64(name string) float64 {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupFloat64(name, fs)
|
||||
}
|
||||
return float64(0)
|
||||
}
|
||||
|
||||
// GlobalDuration looks up the value of a global time.Duration flag, returns 0
|
||||
// if no time.Duration flag exists
|
||||
func (c *Context) GlobalDuration(name string) time.Duration {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupDuration(name, fs)
|
||||
@ -87,7 +102,8 @@ func (c *Context) GlobalDuration(name string) time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Looks up the value of a global bool flag, returns false if no bool flag exists
|
||||
// GlobalBool looks up the value of a global bool flag, returns false if no bool
|
||||
// flag exists
|
||||
func (c *Context) GlobalBool(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBool(name, fs)
|
||||
@ -95,7 +111,17 @@ func (c *Context) GlobalBool(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Looks up the value of a global string flag, returns "" if no string flag exists
|
||||
// GlobalBoolT looks up the value of a global bool flag, returns true if no bool
|
||||
// flag exists
|
||||
func (c *Context) GlobalBoolT(name string) bool {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupBoolT(name, fs)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GlobalString looks up the value of a global string flag, returns "" if no
|
||||
// string flag exists
|
||||
func (c *Context) GlobalString(name string) string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupString(name, fs)
|
||||
@ -103,7 +129,8 @@ func (c *Context) GlobalString(name string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
|
||||
// GlobalStringSlice looks up the value of a global string slice flag, returns
|
||||
// nil if no string slice flag exists
|
||||
func (c *Context) GlobalStringSlice(name string) []string {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupStringSlice(name, fs)
|
||||
@ -111,7 +138,8 @@ func (c *Context) GlobalStringSlice(name string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
|
||||
// GlobalIntSlice looks up the value of a global int slice flag, returns nil if
|
||||
// no int slice flag exists
|
||||
func (c *Context) GlobalIntSlice(name string) []int {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupIntSlice(name, fs)
|
||||
@ -119,7 +147,8 @@ func (c *Context) GlobalIntSlice(name string) []int {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Looks up the value of a global generic flag, returns nil if no generic flag exists
|
||||
// GlobalGeneric looks up the value of a global generic flag, returns nil if no
|
||||
// generic flag exists
|
||||
func (c *Context) GlobalGeneric(name string) interface{} {
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {
|
||||
return lookupGeneric(name, fs)
|
||||
@ -127,12 +156,22 @@ func (c *Context) GlobalGeneric(name string) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the number of flags set
|
||||
// NumFlags returns the number of flags set
|
||||
func (c *Context) NumFlags() int {
|
||||
return c.flagSet.NFlag()
|
||||
}
|
||||
|
||||
// Determines if the flag was actually set
|
||||
// Set sets a context flag to a value.
|
||||
func (c *Context) Set(name, value string) error {
|
||||
return c.flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// GlobalSet sets a context flag to a value on the global flagset
|
||||
func (c *Context) GlobalSet(name, value string) error {
|
||||
return globalContext(c).flagSet.Set(name, value)
|
||||
}
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (c *Context) IsSet(name string) bool {
|
||||
if c.setFlags == nil {
|
||||
c.setFlags = make(map[string]bool)
|
||||
@ -143,7 +182,7 @@ func (c *Context) IsSet(name string) bool {
|
||||
return c.setFlags[name] == true
|
||||
}
|
||||
|
||||
// Determines if the global flag was actually set
|
||||
// GlobalIsSet determines if the global flag was actually set
|
||||
func (c *Context) GlobalIsSet(name string) bool {
|
||||
if c.globalSetFlags == nil {
|
||||
c.globalSetFlags = make(map[string]bool)
|
||||
@ -160,7 +199,7 @@ func (c *Context) GlobalIsSet(name string) bool {
|
||||
return c.globalSetFlags[name]
|
||||
}
|
||||
|
||||
// Returns a slice of flag names used in this context.
|
||||
// FlagNames returns a slice of flag names used in this context.
|
||||
func (c *Context) FlagNames() (names []string) {
|
||||
for _, flag := range c.Command.Flags {
|
||||
name := strings.Split(flag.GetName(), ",")[0]
|
||||
@ -172,7 +211,7 @@ func (c *Context) FlagNames() (names []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a slice of global flag names used by the app.
|
||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
||||
func (c *Context) GlobalFlagNames() (names []string) {
|
||||
for _, flag := range c.App.Flags {
|
||||
name := strings.Split(flag.GetName(), ",")[0]
|
||||
@ -184,20 +223,26 @@ func (c *Context) GlobalFlagNames() (names []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the parent context, if any
|
||||
// Parent returns the parent context, if any
|
||||
func (c *Context) Parent() *Context {
|
||||
return c.parentContext
|
||||
}
|
||||
|
||||
// Args contains apps console arguments
|
||||
type Args []string
|
||||
|
||||
// Returns the command line arguments associated with the context.
|
||||
// Args returns the command line arguments associated with the context.
|
||||
func (c *Context) Args() Args {
|
||||
args := Args(c.flagSet.Args())
|
||||
return args
|
||||
}
|
||||
|
||||
// Returns the nth argument, or else a blank string
|
||||
// NArg returns the number of the command line arguments.
|
||||
func (c *Context) NArg() int {
|
||||
return len(c.Args())
|
||||
}
|
||||
|
||||
// Get returns the nth argument, or else a blank string
|
||||
func (a Args) Get(n int) string {
|
||||
if len(a) > n {
|
||||
return a[n]
|
||||
@ -205,12 +250,12 @@ func (a Args) Get(n int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Returns the first argument, or else a blank string
|
||||
// First returns the first argument, or else a blank string
|
||||
func (a Args) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
// Return the rest of the arguments (not the first one)
|
||||
// Tail returns the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
func (a Args) Tail() []string {
|
||||
if len(a) >= 2 {
|
||||
@ -219,12 +264,12 @@ func (a Args) Tail() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
// Checks if there are any arguments present
|
||||
// Present checks if there are any arguments present
|
||||
func (a Args) Present() bool {
|
||||
return len(a) != 0
|
||||
}
|
||||
|
||||
// Swaps arguments at the given indexes
|
||||
// Swap swaps arguments at the given indexes
|
||||
func (a Args) Swap(from, to int) error {
|
||||
if from >= len(a) || to >= len(a) {
|
||||
return errors.New("index out of range")
|
||||
@ -233,6 +278,19 @@ func (a Args) Swap(from, to int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func globalContext(ctx *Context) *Context {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
if ctx.parentContext == nil {
|
||||
return ctx
|
||||
}
|
||||
ctx = ctx.parentContext
|
||||
}
|
||||
}
|
||||
|
||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet {
|
||||
if ctx.parentContext != nil {
|
||||
ctx = ctx.parentContext
|
92
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/errors.go
generated
vendored
Normal file
92
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/errors.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
||||
var ErrWriter io.Writer = os.Stderr
|
||||
|
||||
// MultiError is an error that wraps multiple errors.
|
||||
type MultiError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
||||
func NewMultiError(err ...error) MultiError {
|
||||
return MultiError{Errors: err}
|
||||
}
|
||||
|
||||
// Error implents the error interface.
|
||||
func (m MultiError) Error() string {
|
||||
errs := make([]string, len(m.Errors))
|
||||
for i, err := range m.Errors {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
||||
|
||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
||||
// code
|
||||
type ExitCoder interface {
|
||||
error
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
||||
type ExitError struct {
|
||||
exitCode int
|
||||
message string
|
||||
}
|
||||
|
||||
// NewExitError makes a new *ExitError
|
||||
func NewExitError(message string, exitCode int) *ExitError {
|
||||
return &ExitError{
|
||||
exitCode: exitCode,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the string message, fulfilling the interface required by
|
||||
// `error`
|
||||
func (ee *ExitError) Error() string {
|
||||
return ee.message
|
||||
}
|
||||
|
||||
// ExitCode returns the exit code, fulfilling the interface required by
|
||||
// `ExitCoder`
|
||||
func (ee *ExitError) ExitCode() int {
|
||||
return ee.exitCode
|
||||
}
|
||||
|
||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
||||
// given exit code. If the given error is a MultiError, then this func is
|
||||
// called on all members of the Errors slice.
|
||||
func HandleExitCoder(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if err.Error() != "" {
|
||||
fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
if multiErr, ok := err.(MultiError); ok {
|
||||
for _, merr := range multiErr.Errors {
|
||||
HandleExitCoder(merr)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,24 +4,28 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This flag enables bash-completion for all commands and subcommands
|
||||
const defaultPlaceholder = "value"
|
||||
|
||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
||||
var BashCompletionFlag = BoolFlag{
|
||||
Name: "generate-bash-completion",
|
||||
Name: "generate-bash-completion",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// This flag prints the version for the application
|
||||
// VersionFlag prints the version for the application
|
||||
var VersionFlag = BoolFlag{
|
||||
Name: "version, v",
|
||||
Usage: "print the version",
|
||||
}
|
||||
|
||||
// This flag prints the help for all commands and subcommands
|
||||
// HelpFlag prints the help for all commands and subcommands
|
||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||
// unless HideHelp is set to true)
|
||||
var HelpFlag = BoolFlag{
|
||||
@ -29,6 +33,10 @@ var HelpFlag = BoolFlag{
|
||||
Usage: "show help",
|
||||
}
|
||||
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
@ -68,24 +76,14 @@ type GenericFlag struct {
|
||||
Value Generic
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the string representation of the generic flag to display the
|
||||
// help text to the user (uses the String() method of the generic flag to show
|
||||
// the value)
|
||||
func (f GenericFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage))
|
||||
}
|
||||
|
||||
func (f GenericFlag) FormatValueHelp() string {
|
||||
if f.Value == nil {
|
||||
return ""
|
||||
}
|
||||
s := f.Value.String()
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", s)
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||
@ -107,6 +105,7 @@ func (f GenericFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of a flag.
|
||||
func (f GenericFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -130,20 +129,19 @@ func (f *StringSlice) Value() []string {
|
||||
return *f
|
||||
}
|
||||
|
||||
// StringSlice is a string flag that can be specified multiple times on the
|
||||
// StringSliceFlag is a string flag that can be specified multiple times on the
|
||||
// command-line
|
||||
type StringSliceFlag struct {
|
||||
Name string
|
||||
Value *StringSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringSliceFlag) String() string {
|
||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||
pref := prefixFor(firstName)
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -171,11 +169,12 @@ func (f StringSliceFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of a flag.
|
||||
func (f StringSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
// StringSlice is an opaque type for []int to satisfy flag.Value
|
||||
// IntSlice is an opaque type for []int to satisfy flag.Value
|
||||
type IntSlice []int
|
||||
|
||||
// Set parses the value into an integer and appends it to the list of values
|
||||
@ -183,9 +182,8 @@ func (f *IntSlice) Set(value string) error {
|
||||
tmp, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
*f = append(*f, tmp)
|
||||
}
|
||||
*f = append(*f, tmp)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -206,13 +204,12 @@ type IntSliceFlag struct {
|
||||
Value *IntSlice
|
||||
Usage string
|
||||
EnvVar string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f IntSliceFlag) String() string {
|
||||
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ")
|
||||
pref := prefixFor(firstName)
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -226,7 +223,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
s = strings.TrimSpace(s)
|
||||
err := newVal.Set(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
fmt.Fprintf(ErrWriter, err.Error())
|
||||
}
|
||||
}
|
||||
f.Value = newVal
|
||||
@ -243,6 +240,7 @@ func (f IntSliceFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f IntSliceFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -253,11 +251,12 @@ type BoolFlag struct {
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *bool
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f BoolFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -285,6 +284,7 @@ func (f BoolFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f BoolFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -296,11 +296,12 @@ type BoolTFlag struct {
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *bool
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f BoolTFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -328,6 +329,7 @@ func (f BoolTFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f BoolTFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -339,19 +341,12 @@ type StringFlag struct {
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *string
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f StringFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage))
|
||||
}
|
||||
|
||||
func (f StringFlag) FormatValueHelp() string {
|
||||
s := f.Value
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("\"%s\"", s)
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -375,6 +370,7 @@ func (f StringFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f StringFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -387,11 +383,12 @@ type IntFlag struct {
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *int
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f IntFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -418,6 +415,7 @@ func (f IntFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f IntFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -430,11 +428,12 @@ type DurationFlag struct {
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *time.Duration
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f DurationFlag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -461,6 +460,7 @@ func (f DurationFlag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f DurationFlag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
@ -473,11 +473,12 @@ type Float64Flag struct {
|
||||
Usage string
|
||||
EnvVar string
|
||||
Destination *float64
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
// String returns the usage
|
||||
func (f Float64Flag) String() string {
|
||||
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage))
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment
|
||||
@ -503,10 +504,21 @@ func (f Float64Flag) Apply(set *flag.FlagSet) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f Float64Flag) GetName() string {
|
||||
return f.Name
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
visible := []Flag{}
|
||||
for _, flag := range fl {
|
||||
if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() {
|
||||
visible = append(visible, flag)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
@ -517,16 +529,37 @@ func prefixFor(name string) (prefix string) {
|
||||
return
|
||||
}
|
||||
|
||||
func prefixedNames(fullName string) (prefixed string) {
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
||||
func unquoteUsage(usage string) (string, string) {
|
||||
for i := 0; i < len(usage); i++ {
|
||||
if usage[i] == '`' {
|
||||
for j := i + 1; j < len(usage); j++ {
|
||||
if usage[j] == '`' {
|
||||
name := usage[i+1 : j]
|
||||
usage = usage[:i] + name + usage[j+1:]
|
||||
return name, usage
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", usage
|
||||
}
|
||||
|
||||
func prefixedNames(fullName, placeholder string) string {
|
||||
var prefixed string
|
||||
parts := strings.Split(fullName, ",")
|
||||
for i, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
prefixed += prefixFor(name) + name
|
||||
if placeholder != "" {
|
||||
prefixed += " " + placeholder
|
||||
}
|
||||
if i < len(parts)-1 {
|
||||
prefixed += ", "
|
||||
}
|
||||
}
|
||||
return
|
||||
return prefixed
|
||||
}
|
||||
|
||||
func withEnvHint(envVar, str string) string {
|
||||
@ -544,3 +577,83 @@ func withEnvHint(envVar, str string) string {
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
fv := reflect.ValueOf(f)
|
||||
|
||||
switch f.(type) {
|
||||
case IntSliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyIntSliceFlag(f.(IntSliceFlag)))
|
||||
case StringSliceFlag:
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
stringifyStringSliceFlag(f.(StringSliceFlag)))
|
||||
}
|
||||
|
||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String())
|
||||
|
||||
needsPlaceholder := false
|
||||
defaultValueString := ""
|
||||
val := fv.FieldByName("Value")
|
||||
|
||||
if val.IsValid() {
|
||||
needsPlaceholder = true
|
||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface())
|
||||
|
||||
if val.Kind() == reflect.String && val.String() != "" {
|
||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String())
|
||||
}
|
||||
}
|
||||
|
||||
if defaultValueString == " (default: )" {
|
||||
defaultValueString = ""
|
||||
}
|
||||
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
|
||||
|
||||
return withEnvHint(fv.FieldByName("EnvVar").String(),
|
||||
fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault))
|
||||
}
|
||||
|
||||
func stringifyIntSliceFlag(f IntSliceFlag) string {
|
||||
defaultVals := []string{}
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, i := range f.Value.Value() {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifyStringSliceFlag(f StringSliceFlag) string {
|
||||
defaultVals := []string{}
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 {
|
||||
for _, s := range f.Value.Value() {
|
||||
if len(s) > 0 {
|
||||
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals)
|
||||
}
|
||||
|
||||
func stringifySliceFlag(usage, name string, defaultVals []string) string {
|
||||
placeholder, usage := unquoteUsage(usage)
|
||||
if placeholder == "" {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
|
||||
defaultVal := ""
|
||||
if len(defaultVals) > 0 {
|
||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", "))
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal))
|
||||
return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault)
|
||||
}
|
28
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/funcs.go
generated
vendored
Normal file
28
Godeps/_workspace/src/gopkg.in/urfave/cli.v1/funcs.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
package cli
|
||||
|
||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
||||
type BashCompleteFunc func(*Context)
|
||||
|
||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
||||
type BeforeFunc func(*Context) error
|
||||
|
||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
||||
// subcommand has finished it is run even if Action() panics
|
||||
type AfterFunc func(*Context) error
|
||||
|
||||
// ActionFunc is the action to execute when no subcommands are specified
|
||||
type ActionFunc func(*Context) error
|
||||
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||
type CommandNotFoundFunc func(*Context, string)
|
||||
|
||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
||||
// customized usage error messages. This function is able to replace the
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// is displayed and the execution is interrupted.
|
||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
|
||||
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||
// expected to be a single line.
|
||||
type FlagStringFunc func(Flag) string
|
@ -3,68 +3,74 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// The text template for the Default help topic.
|
||||
// AppHelpTemplate is the text template for the Default help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var AppHelpTemplate = `NAME:
|
||||
{{.Name}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
||||
{{if .Version}}
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
|
||||
{{if .Version}}{{if not .HideVersion}}
|
||||
VERSION:
|
||||
{{.Version}}
|
||||
{{end}}{{if len .Authors}}
|
||||
{{end}}{{end}}{{if len .Authors}}
|
||||
AUTHOR(S):
|
||||
{{range .Authors}}{{ . }}{{end}}
|
||||
{{end}}{{if .Commands}}
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{end}}{{if .Flags}}
|
||||
{{range .Authors}}{{.}}{{end}}
|
||||
{{end}}{{if .VisibleCommands}}
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
||||
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}}
|
||||
{{end}}{{end}}{{if .VisibleFlags}}
|
||||
GLOBAL OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright }}
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}{{if .Copyright}}
|
||||
COPYRIGHT:
|
||||
{{.Copyright}}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
// The text template for the command help topic.
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}}
|
||||
{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .Flags}}
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{end}}{{ end }}
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
// The text template for the subcommand help topic.
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var SubcommandHelpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||
|
||||
COMMANDS:
|
||||
{{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
|
||||
{{end}}{{if .Flags}}
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}}
|
||||
{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{"\t"}}{{.Usage}}{{end}}
|
||||
{{end}}{{if .VisibleFlags}}
|
||||
OPTIONS:
|
||||
{{range .Flags}}{{.}}
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
@ -73,13 +79,14 @@ var helpCommand = Command{
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) {
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
ShowCommandHelp(c, args.First())
|
||||
} else {
|
||||
ShowAppHelp(c)
|
||||
return ShowCommandHelp(c, args.First())
|
||||
}
|
||||
|
||||
ShowAppHelp(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@ -88,65 +95,73 @@ var helpSubcommand = Command{
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *Context) {
|
||||
Action: func(c *Context) error {
|
||||
args := c.Args()
|
||||
if args.Present() {
|
||||
ShowCommandHelp(c, args.First())
|
||||
} else {
|
||||
ShowSubcommandHelp(c)
|
||||
return ShowCommandHelp(c, args.First())
|
||||
}
|
||||
|
||||
return ShowSubcommandHelp(c)
|
||||
},
|
||||
}
|
||||
|
||||
// Prints help for the App or Command
|
||||
type helpPrinter func(w io.Writer, templ string, data interface{})
|
||||
|
||||
// HelpPrinter is a function that writes the help output. If not set a default
|
||||
// is used. The function signature is:
|
||||
// func(w io.Writer, templ string, data interface{})
|
||||
var HelpPrinter helpPrinter = printHelp
|
||||
|
||||
// Prints version for the App
|
||||
// VersionPrinter prints the version for the App
|
||||
var VersionPrinter = printVersion
|
||||
|
||||
// ShowAppHelp is an action that displays the help.
|
||||
func ShowAppHelp(c *Context) {
|
||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
|
||||
}
|
||||
|
||||
// Prints the list of subcommands as the default app completion method
|
||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
||||
func DefaultAppComplete(c *Context) {
|
||||
for _, command := range c.App.Commands {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
for _, name := range command.Names() {
|
||||
fmt.Fprintln(c.App.Writer, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prints help for the given command
|
||||
func ShowCommandHelp(ctx *Context, command string) {
|
||||
// ShowCommandHelp prints help for the given command
|
||||
func ShowCommandHelp(ctx *Context, command string) error {
|
||||
// show the subcommand help for a command with subcommands
|
||||
if command == "" {
|
||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, c := range ctx.App.Commands {
|
||||
if c.HasName(command) {
|
||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.App.CommandNotFound != nil {
|
||||
ctx.App.CommandNotFound(ctx, command)
|
||||
} else {
|
||||
fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command)
|
||||
if ctx.App.CommandNotFound == nil {
|
||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3)
|
||||
}
|
||||
|
||||
ctx.App.CommandNotFound(ctx, command)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) {
|
||||
ShowCommandHelp(c, c.Command.Name)
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
func ShowSubcommandHelp(c *Context) error {
|
||||
return ShowCommandHelp(c, c.Command.Name)
|
||||
}
|
||||
|
||||
// Prints the version number of the App
|
||||
// ShowVersion prints the version number of the App
|
||||
func ShowVersion(c *Context) {
|
||||
VersionPrinter(c)
|
||||
}
|
||||
@ -155,7 +170,7 @@ func printVersion(c *Context) {
|
||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
|
||||
}
|
||||
|
||||
// Prints the lists of commands within a given context
|
||||
// ShowCompletions prints the lists of commands within a given context
|
||||
func ShowCompletions(c *Context) {
|
||||
a := c.App
|
||||
if a != nil && a.BashComplete != nil {
|
||||
@ -163,7 +178,7 @@ func ShowCompletions(c *Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Prints the custom completions for a given command
|
||||
// ShowCommandCompletions prints the custom completions for a given command
|
||||
func ShowCommandCompletions(ctx *Context, command string) {
|
||||
c := ctx.App.Command(command)
|
||||
if c != nil && c.BashComplete != nil {
|
||||
@ -181,7 +196,10 @@ func printHelp(out io.Writer, templ string, data interface{}) {
|
||||
err := t.Execute(w, data)
|
||||
if err != nil {
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
||||
// we can do to recover. We could send this to os.Stderr if we need.
|
||||
// we can do to recover.
|
||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
w.Flush()
|
103
Makefile
103
Makefile
@ -2,8 +2,8 @@
|
||||
# with Go source code. If you know what GOPATH is then you probably
|
||||
# don't need to bother with make.
|
||||
|
||||
.PHONY: geth geth-cross evm all test travis-test-with-coverage xgo clean
|
||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64
|
||||
.PHONY: geth geth-cross evm all test clean
|
||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
|
||||
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
|
||||
.PHONY: geth-windows geth-windows-386 geth-windows-amd64
|
||||
@ -13,25 +13,41 @@ GOBIN = build/bin
|
||||
GO ?= latest
|
||||
|
||||
geth:
|
||||
build/env.sh go install -v $(shell build/flags.sh) ./cmd/geth
|
||||
build/env.sh go run build/ci.go install ./cmd/geth
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||
|
||||
evm:
|
||||
build/env.sh go run build/ci.go install ./cmd/evm
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/evm to start the evm."
|
||||
|
||||
all:
|
||||
build/env.sh go run build/ci.go install
|
||||
|
||||
test: all
|
||||
build/env.sh go run build/ci.go test
|
||||
|
||||
clean:
|
||||
rm -fr build/_workspace/pkg/ Godeps/_workspace/pkg $(GOBIN)/*
|
||||
|
||||
# Cross Compilation Targets (xgo)
|
||||
|
||||
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||
@echo "Full cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-*
|
||||
|
||||
geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm
|
||||
geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm geth-linux-mips64 geth-linux-mips64le
|
||||
@echo "Linux cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-*
|
||||
|
||||
geth-linux-386: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-386:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/386 -v ./cmd/geth
|
||||
@echo "Linux 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep 386
|
||||
|
||||
geth-linux-amd64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-amd64:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/amd64 -v ./cmd/geth
|
||||
@echo "Linux amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep amd64
|
||||
|
||||
@ -39,37 +55,47 @@ geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-ar
|
||||
@echo "Linux ARM cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm
|
||||
|
||||
geth-linux-arm-5: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm-5:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-5 -v ./cmd/geth
|
||||
@echo "Linux ARMv5 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-5
|
||||
|
||||
geth-linux-arm-6: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm-6:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-6 -v ./cmd/geth
|
||||
@echo "Linux ARMv6 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-6
|
||||
|
||||
geth-linux-arm-7: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm-7:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm-7 -v ./cmd/geth
|
||||
@echo "Linux ARMv7 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm-7
|
||||
|
||||
geth-linux-arm64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-linux-arm64:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/arm64 -v ./cmd/geth
|
||||
@echo "Linux ARM64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep arm64
|
||||
|
||||
geth-linux-mips64:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/mips64 -v ./cmd/geth
|
||||
@echo "Linux MIPS64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips64
|
||||
|
||||
geth-linux-mips64le:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=linux/mips64le -v ./cmd/geth
|
||||
@echo "Linux MIPS64le cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-linux-* | grep mips64le
|
||||
|
||||
geth-darwin: geth-darwin-386 geth-darwin-amd64
|
||||
@echo "Darwin cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-*
|
||||
|
||||
geth-darwin-386: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-darwin-386:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/386 -v ./cmd/geth
|
||||
@echo "Darwin 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-* | grep 386
|
||||
|
||||
geth-darwin-amd64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-darwin-amd64:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=darwin/amd64 -v ./cmd/geth
|
||||
@echo "Darwin amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-darwin-* | grep amd64
|
||||
|
||||
@ -77,43 +103,22 @@ geth-windows: geth-windows-386 geth-windows-amd64
|
||||
@echo "Windows cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-*
|
||||
|
||||
geth-windows-386: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-windows-386:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/386 -v ./cmd/geth
|
||||
@echo "Windows 386 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-* | grep 386
|
||||
|
||||
geth-windows-amd64: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-windows-amd64:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=windows/amd64 -v ./cmd/geth
|
||||
@echo "Windows amd64 cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-windows-* | grep amd64
|
||||
|
||||
geth-android: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-android:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=android-21/aar -v ./cmd/geth
|
||||
@echo "Android cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-android-*
|
||||
|
||||
geth-ios: xgo
|
||||
build/env.sh $(GOBIN)/xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v $(shell build/flags.sh) ./cmd/geth
|
||||
geth-ios:
|
||||
build/env.sh go run build/ci.go xgo --go=$(GO) --dest=$(GOBIN) --targets=ios-7.0/framework -v ./cmd/geth
|
||||
@echo "iOS framework cross compilation done:"
|
||||
@ls -ld $(GOBIN)/geth-ios-*
|
||||
|
||||
evm:
|
||||
build/env.sh $(GOROOT)/bin/go install -v $(shell build/flags.sh) ./cmd/evm
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/evm to start the evm."
|
||||
|
||||
all:
|
||||
build/env.sh go install -v $(shell build/flags.sh) ./...
|
||||
|
||||
test: all
|
||||
build/env.sh go test ./...
|
||||
|
||||
travis-test-with-coverage: all
|
||||
build/env.sh go vet ./...
|
||||
build/env.sh build/test-global-coverage.sh
|
||||
|
||||
xgo:
|
||||
build/env.sh go get github.com/karalabe/xgo
|
||||
|
||||
clean:
|
||||
rm -fr build/_workspace/pkg/ Godeps/_workspace/pkg $(GOBIN)/*
|
||||
|
256
README.md
256
README.md
@ -9,7 +9,7 @@ master | [](https://godoc.org/github.com/ethereum/go-ethereum)
|
||||
)](https://godoc.org/github.com/ethereum/go-ethereum)
|
||||
[](https://gitter.im/ethereum/go-ethereum?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
## Automated development builds
|
||||
@ -36,38 +36,246 @@ Once the dependencies are installed, run
|
||||
|
||||
make geth
|
||||
|
||||
or, to build the full suite of utilities:
|
||||
|
||||
make all
|
||||
|
||||
## Executables
|
||||
|
||||
Go Ethereum comes with several wrappers/executables found in
|
||||
[the `cmd` directory](https://github.com/ethereum/go-ethereum/tree/develop/cmd):
|
||||
The go-ethereum project comes with several wrappers/executables found in the `cmd` directory.
|
||||
|
||||
Command | |
|
||||
----------|---------|
|
||||
`geth` | Ethereum CLI (ethereum command line interface client) |
|
||||
`bootnode` | runs a bootstrap node for the Discovery Protocol |
|
||||
`ethtest` | test tool which runs with the [tests](https://github.com/ethereum/tests) suite: `/path/to/test.json > ethtest --test BlockTests --stdin`.
|
||||
`evm` | is a generic Ethereum Virtual Machine: `evm -code 60ff60ff -gas 10000 -price 0 -dump`. See `-h` for a detailed description. |
|
||||
`disasm` | disassembles EVM code: `echo "6001" | disasm` |
|
||||
`rlpdump` | prints RLP structures |
|
||||
| Command | Description |
|
||||
|:----------:|-------------|
|
||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default) archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as an gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. Please see our [Command Line Options](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) wiki page for details. |
|
||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||
| `disasm` | Bytecode disassembler to convert EVM (Ethereum Virtual Machine) bytecode into more user friendly assembly-like opcodes (e.g. `echo "6001" | disasm`). For details on the individual opcodes, please see pages 22-30 of the [Ethereum Yellow Paper](http://gavwood.com/paper.pdf). |
|
||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow insolated, fine graned debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||
|
||||
## Command line options
|
||||
## Running geth
|
||||
|
||||
`geth` can be configured via command line options, environment variables and config files.
|
||||
Going through all the possible command line flags is out of scope here (please consult our
|
||||
[CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)), but we've
|
||||
enumerated a few common parameter combos to get you up to speed quickly on how you can run your
|
||||
own Geth instance.
|
||||
|
||||
To get the options available:
|
||||
### Full node on the main Ethereum network
|
||||
|
||||
geth help
|
||||
By far the most common scenario is people wanting to simply interact with the Ethereum network:
|
||||
create accounts; transfer funds; deploy and interact with contracts. For this particular use-case
|
||||
the user doesn't care about years-old historical data, so we can fast-sync quickly to the current
|
||||
state of the network. To do so:
|
||||
|
||||
For further details on options, see the [wiki](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)
|
||||
```
|
||||
$ geth --fast --cache=512 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 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).
|
||||
This too is optional and if you leave it out you can always attach to an already running Geth instance
|
||||
with `geth --attach`.
|
||||
|
||||
### Full node on the Ethereum test network
|
||||
|
||||
Transitioning towards developers, if you'd like to play around with creating Ethereum contracts, you
|
||||
almost certainly would like to do that without any real money involved until you get the hang of the
|
||||
entire system. In other words, instead of attaching to the main network, you want to join the **test**
|
||||
network with your node, which is fully equivalent to the main network, but with play-Ether only.
|
||||
|
||||
```
|
||||
$ geth --testnet --fast --cache=512 console
|
||||
```
|
||||
|
||||
The `--fast`, `--cache` flags and `console` subcommand have the exact same meaning as above and they
|
||||
are equially useful on the testnet too. Please see above for their explanations if you've skipped to
|
||||
here.
|
||||
|
||||
Specifying the `--testnet` flag however will reconfigure your Geth instance a bit:
|
||||
|
||||
* Instead of using the default data directory (`~/.ethereum` on Linux for example), Geth will nest
|
||||
itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on Linux).
|
||||
* Instead of connecting the main Ethereum network, the client will connect to the test network,
|
||||
which uses different P2P bootnodes, different network IDs and genesis states.
|
||||
|
||||
*Note: Although there are some internal protective measures to prevent transactions from crossing
|
||||
over between the main network and test network (different starting nonces), you should make sure to
|
||||
always use separate accounts for play-money and real-money. Unless you manually move accounts, Geth
|
||||
will by default correctly separate the two networks and will not make any accounts available between
|
||||
them.*
|
||||
|
||||
### Programatically interfacing Geth nodes
|
||||
|
||||
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
||||
network via your own programs and not manually through the console. To aid this, Geth has built in
|
||||
support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) and
|
||||
[Geth specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). These can be
|
||||
exposed via HTTP, WebSockets and IPC (unix sockets on unix based platroms, and named pipes on Windows).
|
||||
|
||||
The IPC interface is enabled by default and exposes all the APIs supported by Geth, whereas the HTTP
|
||||
and WS interfaces need to manually be enabled and only expose a subset of APIs due to security reasons.
|
||||
These can be turned on/off and configured as you'd expect.
|
||||
|
||||
HTTP based JSON-RPC API options:
|
||||
|
||||
* `--rpc` Enable the HTTP-RPC server
|
||||
* `--rpcaddr` HTTP-RPC server listening interface (default: "localhost")
|
||||
* `--rpcport` HTTP-RPC server listening port (default: 8545)
|
||||
* `--rpcapi` API's offered over the HTTP-RPC interface (default: "eth,net,web3")
|
||||
* `--rpccorsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced)
|
||||
* `--ws` Enable the WS-RPC server
|
||||
* `--wsaddr` WS-RPC server listening interface (default: "localhost")
|
||||
* `--wsport` WS-RPC server listening port (default: 8546)
|
||||
* `--wsapi` API's offered over the WS-RPC interface (default: "eth,net,web3")
|
||||
* `--wsorigins` Origins from which to accept websockets requests
|
||||
* `--ipcdisable` Disable the IPC-RPC server
|
||||
* `--ipcapi` API's offered over the IPC-RPC interface (default: "admin,debug,eth,miner,net,personal,shh,txpool,web3")
|
||||
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
|
||||
|
||||
You'll need to use your own programming environments' capabilities (libraries, tools, etc) to connect
|
||||
via HTTP, WS or IPC to a Geth node configured with the above flags and you'll need to speak [JSON-RPC](http://www.jsonrpc.org/specification)
|
||||
on all transports. You can reuse the same connection for multiple requests!
|
||||
|
||||
**Note: Please understand the security implications of opening up an HTTP/WS based transport before
|
||||
doing so! Hackers on the internet are actively trying to subvert Ethereum nodes with exposed APIs!
|
||||
Further, all browser tabs can access locally running webservers, so malicious webpages could try to
|
||||
subvert locally available APIs!**
|
||||
|
||||
### Operating a private network
|
||||
|
||||
Maintaining your own private network is more involved as a lot of configurations taken for granted in
|
||||
the official networks need to be manually set up.
|
||||
|
||||
#### Defining the private genesis state
|
||||
|
||||
First, you'll need to create the genesis state of your networks, which all nodes need to be aware of
|
||||
and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00"
|
||||
}
|
||||
```
|
||||
|
||||
The above fields should be fine for most purposes, although we'd recommend changing the `nonce` to
|
||||
some random value so you prevent unknown remote nodes from being able to connect to you. If you'd
|
||||
like to pre-fund some accounts for easier testing, you can populate the `alloc` field with account
|
||||
configs:
|
||||
|
||||
```json
|
||||
"alloc": {
|
||||
"0x0000000000000000000000000000000000000001": {"balance": "111111111"},
|
||||
"0x0000000000000000000000000000000000000002": {"balance": "222222222"}
|
||||
}
|
||||
```
|
||||
|
||||
With the genesis state defined in the above JSON file, you'll need to initialize **every** Geth node
|
||||
with it prior to starting it up to ensure all blockchain parameters are correctly set:
|
||||
|
||||
```
|
||||
$ geth init path/to/genesis.json
|
||||
```
|
||||
|
||||
#### Creating the rendezvous point
|
||||
|
||||
With all nodes that you want to run initialized to the desired genesis state, you'll need to start a
|
||||
bootstrap node that others can use to find each other in your network and/or over the internet. The
|
||||
clean way is to configure and run a dedicated bootnode:
|
||||
|
||||
```
|
||||
$ bootnode --genkey=boot.key
|
||||
$ bootnode --nodekey=boot.key
|
||||
```
|
||||
|
||||
With the bootnode online, it will display an [`enode` URL](https://github.com/ethereum/wiki/wiki/enode-url-format)
|
||||
that other nodes can use to connect to it and exchange peer information. Make sure to replace the
|
||||
displayed IP address information (most probably `[::]`) with your externally accessible IP to get the
|
||||
actual `enode` URL.
|
||||
|
||||
*Note: You could also use a full fledged Geth node as a bootnode, but it's the less recommended way.*
|
||||
|
||||
#### Starting up your member nodes
|
||||
|
||||
With the bootnode operational and externally reachable (you can try `telnet <ip> <port>` to ensure
|
||||
it's indeed reachable), start every subsequent Geth node pointed to the bootnode for peer discovery
|
||||
via the `--bootnodes` flag. It will probably also be desirable to keep the data directory of your
|
||||
private network separated, so do also specify a custom `--datadir` flag.
|
||||
|
||||
```
|
||||
$ geth --datadir=path/to/custom/data/folder --bootnodes=<bootnode-enode-url-from-above>
|
||||
```
|
||||
|
||||
*Note: Since your network will be completely cut off from the main and test networks, you'll also
|
||||
need to configure a miner to process transactions and create new blocks for you.*
|
||||
|
||||
#### Running a private miner
|
||||
|
||||
Mining on the public Ethereum network is a complex task as it's only feasible using GPUs, requiring
|
||||
an OpenCL or CUDA enabled `ethminer` instance. For information on such a setup, please consult the
|
||||
[EtherMining subreddit](https://www.reddit.com/r/EtherMining/) and the [Genoil miner](https://github.com/Genoil/cpp-ethereum)
|
||||
repository.
|
||||
|
||||
In a private network setting however, a single CPU miner instance is more than enough for practical
|
||||
purposes as it can produce a stable stream of blocks at the correct intervals without needing heavy
|
||||
resources (consider running on a single thread, no need for multiple ones either). To start a Geth
|
||||
instance for mining, run it with all your usual flags, extended by:
|
||||
|
||||
```
|
||||
$ geth <usual-flags> --mine --minerthreads=1 --etherbase=0x0000000000000000000000000000000000000000
|
||||
```
|
||||
|
||||
Which will start mining bocks and transactions on a single CPU thread, crediting all proceedings to
|
||||
the account specified by `--etherbase`. You can further tune the mining by changing the default gas
|
||||
limit blocks converge to (`--targetgaslimit`) and the price transactions are accepted at (`--gasprice`).
|
||||
|
||||
## Contribution
|
||||
|
||||
If you'd like to contribute to go-ethereum please fork, fix, commit and
|
||||
send a pull request. Commits who do not comply with the coding standards
|
||||
are ignored (use gofmt!). If you send pull requests make absolute sure that you
|
||||
commit on the `develop` branch and that you do not merge to master.
|
||||
Commits that are directly based on master are simply ignored.
|
||||
Thank you for considering to help out with the source code! We welcome contributions from
|
||||
anyone on the internet, and are grateful for even the smallest of fixes!
|
||||
|
||||
If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request
|
||||
for the maintainers to review and merge into the main code base. If you wish to submit more
|
||||
complex changes though, please check up with the core devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum)
|
||||
to ensure those changes are in line with the general philosophy of the project and/or get some
|
||||
early feedback which can make both your efforts much lighter as well as our review and merge
|
||||
procedures quick and simple.
|
||||
|
||||
Please make sure your contributions adhere to our coding guidelines:
|
||||
|
||||
* Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
* Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
* Pull requests need to be based on and opened against the `develop` branch.
|
||||
* Commit messages should be prefixed with the package(s) they modify.
|
||||
* E.g. "eth, rpc: make trace configs optional"
|
||||
|
||||
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||
for more details on configuring your environment, managing project dependencies and testing procedures.
|
||||
|
||||
## License
|
||||
|
||||
The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the
|
||||
[GNU Lesser General Public License v3.0](http://www.gnu.org/licenses/lgpl-3.0.en.html), also
|
||||
included in our repository in the `COPYING.LESSER` file.
|
||||
|
||||
The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the
|
||||
[GNU General Public License v3.0](http://www.gnu.org/licenses/gpl-3.0.en.html), also included
|
||||
in our repository in the `COPYING` file.
|
||||
|
||||
|
||||
See [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||
for more details on configuring your environment, testing, and
|
||||
dependency management.
|
||||
|
@ -48,42 +48,6 @@ func JSON(reader io.Reader) (ABI, error) {
|
||||
return abi, nil
|
||||
}
|
||||
|
||||
// tests, tests whether the given input would result in a successful
|
||||
// call. Checks argument list count and matches input to `input`.
|
||||
func (abi ABI) pack(method Method, args ...interface{}) ([]byte, error) {
|
||||
// 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(a)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("`%s` %v", method.Name, err)
|
||||
}
|
||||
|
||||
// check for a slice type (string, bytes, slice)
|
||||
if input.Type.T == StringTy || input.Type.T == BytesTy || input.Type.IsSlice {
|
||||
// calculate the offset
|
||||
offset := len(method.Inputs)*32 + len(variableInput)
|
||||
// set the offset
|
||||
ret = append(ret, packNum(reflect.ValueOf(offset), UintTy)...)
|
||||
// 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
|
||||
}
|
||||
|
||||
// Pack the given method name to conform the ABI. Method call's data
|
||||
// will consist of method_id, args0, arg1, ... argN. Method id consists
|
||||
// of 4 bytes and arguments are all 32 bytes.
|
||||
@ -102,11 +66,7 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||
}
|
||||
method = m
|
||||
}
|
||||
// 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))
|
||||
}
|
||||
arguments, err := abi.pack(method, args...)
|
||||
arguments, err := method.pack(method, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -126,18 +86,21 @@ func toGoSlice(i int, t Argument, output []byte) (interface{}, error) {
|
||||
if index+32 > len(output) {
|
||||
return nil, fmt.Errorf("abi: cannot marshal in to go slice: insufficient size output %d require %d", len(output), index+32)
|
||||
}
|
||||
elem := t.Type.Elem
|
||||
|
||||
// first we need to create a slice of the type
|
||||
var refSlice reflect.Value
|
||||
switch t.Type.T {
|
||||
switch elem.T {
|
||||
case IntTy, UintTy, BoolTy: // int, uint, bool can all be of type big int.
|
||||
refSlice = reflect.ValueOf([]*big.Int(nil))
|
||||
case AddressTy: // address must be of slice Address
|
||||
refSlice = reflect.ValueOf([]common.Address(nil))
|
||||
case HashTy: // hash must be of slice hash
|
||||
refSlice = reflect.ValueOf([]common.Hash(nil))
|
||||
case FixedBytesTy:
|
||||
refSlice = reflect.ValueOf([]byte(nil))
|
||||
default: // no other types are supported
|
||||
return nil, fmt.Errorf("abi: unsupported slice type %v", t.Type.T)
|
||||
return nil, fmt.Errorf("abi: unsupported slice type %v", elem.T)
|
||||
}
|
||||
// get the offset which determines the start of this array ...
|
||||
offset := int(common.BytesToBig(output[index : index+32]).Uint64())
|
||||
@ -164,7 +127,7 @@ func toGoSlice(i int, t Argument, output []byte) (interface{}, error) {
|
||||
)
|
||||
|
||||
// set inter to the correct type (cast)
|
||||
switch t.Type.T {
|
||||
switch elem.T {
|
||||
case IntTy, UintTy:
|
||||
inter = common.BytesToBig(returnOutput)
|
||||
case BoolTy:
|
||||
@ -186,7 +149,7 @@ func toGoSlice(i int, t Argument, output []byte) (interface{}, error) {
|
||||
// argument in T.
|
||||
func toGoType(i int, t Argument, output []byte) (interface{}, error) {
|
||||
// we need to treat slices differently
|
||||
if t.Type.IsSlice {
|
||||
if (t.Type.IsSlice || t.Type.IsArray) && t.Type.T != BytesTy && t.Type.T != StringTy && t.Type.T != FixedBytesTy {
|
||||
return toGoSlice(i, t, output)
|
||||
}
|
||||
|
||||
@ -217,12 +180,33 @@ func toGoType(i int, t Argument, output []byte) (interface{}, error) {
|
||||
returnOutput = output[index : index+32]
|
||||
}
|
||||
|
||||
// cast bytes to abi return type
|
||||
// convert the bytes to whatever is specified by the ABI.
|
||||
switch t.Type.T {
|
||||
case IntTy:
|
||||
return common.BytesToBig(returnOutput), nil
|
||||
case UintTy:
|
||||
return common.BytesToBig(returnOutput), nil
|
||||
case IntTy, UintTy:
|
||||
bigNum := common.BytesToBig(returnOutput)
|
||||
|
||||
// If the type is a integer convert to the integer type
|
||||
// specified by the ABI.
|
||||
switch t.Type.Kind {
|
||||
case reflect.Uint8:
|
||||
return uint8(bigNum.Uint64()), nil
|
||||
case reflect.Uint16:
|
||||
return uint16(bigNum.Uint64()), nil
|
||||
case reflect.Uint32:
|
||||
return uint32(bigNum.Uint64()), nil
|
||||
case reflect.Uint64:
|
||||
return uint64(bigNum.Uint64()), nil
|
||||
case reflect.Int8:
|
||||
return int8(bigNum.Int64()), nil
|
||||
case reflect.Int16:
|
||||
return int16(bigNum.Int64()), nil
|
||||
case reflect.Int32:
|
||||
return int32(bigNum.Int64()), nil
|
||||
case reflect.Int64:
|
||||
return int64(bigNum.Int64()), nil
|
||||
case reflect.Ptr:
|
||||
return bigNum, nil
|
||||
}
|
||||
case BoolTy:
|
||||
return common.BytesToBig(returnOutput).Uint64() > 0, nil
|
||||
case AddressTy:
|
||||
@ -254,8 +238,16 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error {
|
||||
return fmt.Errorf("abi: unmarshalling empty output")
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(v).Elem()
|
||||
typ := value.Type()
|
||||
// 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 len(method.Outputs) > 1 {
|
||||
switch value.Kind() {
|
||||
@ -284,6 +276,25 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error {
|
||||
return fmt.Errorf("abi: cannot marshal tuple in to slice %T (only []interface{} is supported)", v)
|
||||
}
|
||||
|
||||
// if the slice already contains values, set those instead of the interface slice itself.
|
||||
if value.Len() > 0 {
|
||||
if len(method.Outputs) > value.Len() {
|
||||
return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(method.Outputs), value.Len())
|
||||
}
|
||||
|
||||
for i := 0; i < len(method.Outputs); i++ {
|
||||
marshalledValue, err := toGoType(i, method.Outputs[i], output)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reflectValue := reflect.ValueOf(marshalledValue)
|
||||
if err := set(value.Index(i).Elem(), reflectValue, method.Outputs[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// create a new slice and start appending the unmarshalled
|
||||
// values to the new interface slice.
|
||||
z := reflect.MakeSlice(typ, 0, len(method.Outputs))
|
||||
@ -312,32 +323,6 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// set attempts to assign src to dst by either setting, copying or otherwise.
|
||||
//
|
||||
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
||||
// strict ruleset as bare `reflect` does.
|
||||
func set(dst, src reflect.Value, output Argument) error {
|
||||
dstType := dst.Type()
|
||||
srcType := src.Type()
|
||||
|
||||
switch {
|
||||
case dstType.AssignableTo(src.Type()):
|
||||
dst.Set(src)
|
||||
case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice:
|
||||
if !dstType.Elem().AssignableTo(r_byte) {
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to array of elem %v", src.Type(), dstType.Elem())
|
||||
}
|
||||
|
||||
if dst.Len() < output.Type.Size {
|
||||
return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.Size, dst.Len())
|
||||
}
|
||||
reflect.Copy(dst, src)
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||
var fields []struct {
|
||||
Type string
|
||||
|
@ -29,66 +29,422 @@ import (
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// formatSilceOutput add padding to the value and adds a size
|
||||
func formatSliceOutput(v ...[]byte) []byte {
|
||||
off := common.LeftPadBytes(big.NewInt(int64(len(v))).Bytes(), 32)
|
||||
output := append(off, make([]byte, 0, len(v)*32)...)
|
||||
|
||||
for _, value := range v {
|
||||
output = append(output, common.LeftPadBytes(value, 32)...)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// quick helper padding
|
||||
func pad(input []byte, size int, left bool) []byte {
|
||||
if left {
|
||||
return common.LeftPadBytes(input, size)
|
||||
}
|
||||
return common.RightPadBytes(input, size)
|
||||
}
|
||||
|
||||
func TestTypeCheck(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
typ string
|
||||
input interface{}
|
||||
err string
|
||||
}{
|
||||
{"uint", big.NewInt(1), ""},
|
||||
{"int", big.NewInt(1), ""},
|
||||
{"uint30", big.NewInt(1), ""},
|
||||
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||
{"uint16", uint16(1), ""},
|
||||
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||
{"uint16[]", []uint16{1, 2, 3}, ""},
|
||||
{"uint16[]", [3]uint16{1, 2, 3}, ""},
|
||||
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type []uint16 as argument"},
|
||||
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
||||
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||
{"uint16[3]", []uint16{1, 2, 3}, ""},
|
||||
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||
{"address[]", []common.Address{common.Address{1}}, ""},
|
||||
{"address[1]", []common.Address{common.Address{1}}, ""},
|
||||
{"address[1]", [1]common.Address{common.Address{1}}, ""},
|
||||
{"address[2]", [1]common.Address{common.Address{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||
{"bytes32", [32]byte{}, ""},
|
||||
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||
{"bytes32", common.Hash{1}, ""},
|
||||
{"bytes31", [31]byte{}, ""},
|
||||
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||
{"bytes", []byte{0, 1}, ""},
|
||||
{"bytes", [2]byte{0, 1}, ""},
|
||||
{"bytes", common.Hash{1}, ""},
|
||||
{"string", "hello world", ""},
|
||||
{"bytes32[]", [][32]byte{[32]byte{}}, ""},
|
||||
} {
|
||||
typ, err := NewType(test.typ)
|
||||
if err != nil {
|
||||
t.Fatal("unexpected parse error:", err)
|
||||
}
|
||||
|
||||
err = typeCheck(typ, reflect.ValueOf(test.input))
|
||||
if err != nil && len(test.err) == 0 {
|
||||
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if err == nil && len(test.err) != 0 {
|
||||
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleMethodUnpack(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
def string // definition of the **output** ABI params
|
||||
marshalledOutput []byte // evm return data
|
||||
expectedOut interface{} // the expected output
|
||||
outVar string // the output variable (e.g. uint32, *big.Int, etc)
|
||||
err string // empty or error if expected
|
||||
}{
|
||||
{
|
||||
`[ { "type": "uint32" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
uint32(1),
|
||||
"uint32",
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[ { "type": "uint32" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
nil,
|
||||
"uint16",
|
||||
"abi: cannot unmarshal uint32 in to uint16",
|
||||
},
|
||||
{
|
||||
`[ { "type": "uint17" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
nil,
|
||||
"uint16",
|
||||
"abi: cannot unmarshal *big.Int in to uint16",
|
||||
},
|
||||
{
|
||||
`[ { "type": "uint17" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
big.NewInt(1),
|
||||
"*big.Int",
|
||||
"",
|
||||
},
|
||||
|
||||
{
|
||||
`[ { "type": "int32" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
int32(1),
|
||||
"int32",
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[ { "type": "int32" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
nil,
|
||||
"int16",
|
||||
"abi: cannot unmarshal int32 in to int16",
|
||||
},
|
||||
{
|
||||
`[ { "type": "int17" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
nil,
|
||||
"int16",
|
||||
"abi: cannot unmarshal *big.Int in to int16",
|
||||
},
|
||||
{
|
||||
`[ { "type": "int17" } ]`,
|
||||
pad([]byte{1}, 32, true),
|
||||
big.NewInt(1),
|
||||
"*big.Int",
|
||||
"",
|
||||
},
|
||||
|
||||
{
|
||||
`[ { "type": "address" } ]`,
|
||||
pad(pad([]byte{1}, 20, false), 32, true),
|
||||
common.Address{1},
|
||||
"address",
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[ { "type": "bytes32" } ]`,
|
||||
pad([]byte{1}, 32, false),
|
||||
pad([]byte{1}, 32, false),
|
||||
"bytes",
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[ { "type": "bytes32" } ]`,
|
||||
pad([]byte{1}, 32, false),
|
||||
pad([]byte{1}, 32, false),
|
||||
"hash",
|
||||
"",
|
||||
},
|
||||
{
|
||||
`[ { "type": "bytes32" } ]`,
|
||||
pad([]byte{1}, 32, false),
|
||||
pad([]byte{1}, 32, false),
|
||||
"interface",
|
||||
"",
|
||||
},
|
||||
} {
|
||||
abiDefinition := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
||||
abi, err := JSON(strings.NewReader(abiDefinition))
|
||||
if err != nil {
|
||||
t.Errorf("%d failed. %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var outvar interface{}
|
||||
switch test.outVar {
|
||||
case "uint8":
|
||||
var v uint8
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "uint16":
|
||||
var v uint16
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "uint32":
|
||||
var v uint32
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "uint64":
|
||||
var v uint64
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "int8":
|
||||
var v int8
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "int16":
|
||||
var v int16
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "int32":
|
||||
var v int32
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "int64":
|
||||
var v int64
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "*big.Int":
|
||||
var v *big.Int
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "address":
|
||||
var v common.Address
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "bytes":
|
||||
var v []byte
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "hash":
|
||||
var v common.Hash
|
||||
err = abi.Unpack(&v, "method", test.marshalledOutput)
|
||||
outvar = v
|
||||
case "interface":
|
||||
err = abi.Unpack(&outvar, "method", test.marshalledOutput)
|
||||
default:
|
||||
t.Errorf("unsupported type '%v' please add it to the switch statement in this test", test.outVar)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil && len(test.err) == 0 {
|
||||
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if err == nil && len(test.err) != 0 {
|
||||
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
||||
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// bit of an ugly hack for hash type but I don't feel like finding a proper solution
|
||||
if test.outVar == "hash" {
|
||||
tmp := outvar.(common.Hash) // without assignment it's unaddressable
|
||||
outvar = tmp[:]
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(test.expectedOut, outvar) {
|
||||
t.Errorf("%d failed. Output error: expected %v, got %v", i, test.expectedOut, outvar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackSetInterfaceSlice(t *testing.T) {
|
||||
var (
|
||||
var1 = new(uint8)
|
||||
var2 = new(uint8)
|
||||
)
|
||||
out := []interface{}{var1, var2}
|
||||
abi, err := JSON(strings.NewReader(`[{"type":"function", "name":"ints", "outputs":[{"type":"uint8"}, {"type":"uint8"}]}]`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
marshalledReturn := append(pad([]byte{1}, 32, true), pad([]byte{2}, 32, true)...)
|
||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if *var1 != 1 {
|
||||
t.Error("expected var1 to be 1, got", *var1)
|
||||
}
|
||||
if *var2 != 2 {
|
||||
t.Error("expected var2 to be 2, got", *var2)
|
||||
}
|
||||
|
||||
out = []interface{}{var1}
|
||||
err = abi.Unpack(&out, "ints", marshalledReturn)
|
||||
|
||||
expErr := "abi: cannot marshal in to slices of unequal size (require: 2, got: 1)"
|
||||
if err == nil || err.Error() != expErr {
|
||||
t.Error("expected err:", expErr, "Got:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
for i, test := range []struct {
|
||||
typ string
|
||||
|
||||
input interface{}
|
||||
output []byte
|
||||
}{
|
||||
{"uint16", uint16(2), pad([]byte{2}, 32, true)},
|
||||
{"uint16[]", []uint16{1, 2}, formatSliceOutput([]byte{1}, []byte{2})},
|
||||
{"bytes20", [20]byte{1}, pad([]byte{1}, 32, false)},
|
||||
{"uint256[]", []*big.Int{big.NewInt(1), big.NewInt(2)}, formatSliceOutput([]byte{1}, []byte{2})},
|
||||
{"address[]", []common.Address{common.Address{1}, common.Address{2}}, formatSliceOutput(pad([]byte{1}, 20, false), pad([]byte{2}, 20, false))},
|
||||
{"bytes32[]", []common.Hash{common.Hash{1}, common.Hash{2}}, formatSliceOutput(pad([]byte{1}, 32, false), pad([]byte{2}, 32, false))},
|
||||
} {
|
||||
typ, err := NewType(test.typ)
|
||||
if err != nil {
|
||||
t.Fatal("unexpected parse error:", err)
|
||||
}
|
||||
|
||||
output, err := typ.pack(reflect.ValueOf(test.input))
|
||||
if err != nil {
|
||||
t.Fatal("unexpected pack error:", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(output, test.output) {
|
||||
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethodPack(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sig := abi.Methods["slice"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
|
||||
packed, err := abi.Pack("slice", []uint32{1, 2})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
var addrA, addrB = common.Address{1}, common.Address{2}
|
||||
sig = abi.Methods["sliceAddress"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||
|
||||
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
var addrC, addrD = common.Address{3}, common.Address{4}
|
||||
sig = abi.Methods["sliceMultiAddress"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||
|
||||
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
sig = abi.Methods["slice256"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
|
||||
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
}
|
||||
|
||||
const jsondata = `
|
||||
[
|
||||
{ "type" : "function", "name" : "balance", "const" : true },
|
||||
{ "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
|
||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
||||
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }
|
||||
]`
|
||||
|
||||
const jsondata2 = `
|
||||
[
|
||||
{ "type" : "function", "name" : "balance", "const" : true },
|
||||
{ "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
|
||||
{ "type" : "function", "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
|
||||
{ "type" : "function", "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
|
||||
{ "type" : "function", "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
|
||||
{ "type" : "function", "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
|
||||
{ "type" : "function", "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] },
|
||||
{ "type" : "function", "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
|
||||
{ "type" : "function", "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
|
||||
{ "type" : "function", "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
|
||||
{ "type" : "function", "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
|
||||
{ "type" : "function", "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
|
||||
{ "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
|
||||
{ "type" : "function", "name" : "sliceAddress", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
|
||||
{ "type" : "function", "name" : "sliceMultiAddress", "const" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }
|
||||
{ "type" : "function", "name" : "balance", "constant" : true },
|
||||
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
|
||||
{ "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] },
|
||||
{ "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] },
|
||||
{ "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] },
|
||||
{ "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] },
|
||||
{ "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] },
|
||||
{ "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] },
|
||||
{ "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] },
|
||||
{ "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] },
|
||||
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
|
||||
{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
|
||||
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
|
||||
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }
|
||||
]`
|
||||
|
||||
func TestType(t *testing.T) {
|
||||
typ, err := NewType("uint32")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if typ.Kind != reflect.Uint {
|
||||
t.Error("expected uint32 to have kind Ptr")
|
||||
}
|
||||
|
||||
typ, err = NewType("uint32[]")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !typ.IsSlice {
|
||||
t.Error("expected uint32[] to be slice")
|
||||
}
|
||||
if typ.Type != ubig_t {
|
||||
t.Error("expcted uith32[] to have type uint64")
|
||||
}
|
||||
|
||||
typ, err = NewType("uint32[2]")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !typ.IsSlice {
|
||||
t.Error("expected uint32[2] to be slice")
|
||||
}
|
||||
if typ.Type != ubig_t {
|
||||
t.Error("expcted uith32[2] to have type uint64")
|
||||
}
|
||||
if typ.SliceSize != 2 {
|
||||
t.Error("expected uint32[2] to have a size of 2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
Uint256, _ := NewType("uint256")
|
||||
exp := ABI{
|
||||
@ -164,21 +520,6 @@ func TestTestString(t *testing.T) {
|
||||
if _, err := abi.Pack("string", "hello world"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
str10 := string(make([]byte, 10))
|
||||
if _, err := abi.Pack("string32", str10); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
str32 := string(make([]byte, 32))
|
||||
if _, err := abi.Pack("string32", str32); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
str33 := string(make([]byte, 33))
|
||||
if _, err := abi.Pack("string32", str33); err == nil {
|
||||
t.Error("expected str33 to throw out of bound error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestBool(t *testing.T) {
|
||||
@ -210,26 +551,10 @@ func TestTestSlice(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestImplicitTypeCasts(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
slice := make([]uint8, 2)
|
||||
_, err = abi.Pack("uint64[2]", slice)
|
||||
expStr := "`uint64[2]` abi: cannot use type uint8 as type uint64"
|
||||
if err.Error() != expStr {
|
||||
t.Errorf("expected %v, got %v", expStr, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMethodSignature(t *testing.T) {
|
||||
String, _ := NewType("string")
|
||||
String32, _ := NewType("string32")
|
||||
m := Method{"foo", false, []Argument{Argument{"bar", String32, false}, Argument{"baz", String, false}}, nil}
|
||||
exp := "foo(string32,string)"
|
||||
m := Method{"foo", false, []Argument{Argument{"bar", String, false}, Argument{"baz", String, false}}, nil}
|
||||
exp := "foo(string,string)"
|
||||
if m.Sig() != exp {
|
||||
t.Error("signature mismatch", exp, "!=", m.Sig())
|
||||
}
|
||||
@ -247,28 +572,6 @@ func TestMethodSignature(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
sig := crypto.Keccak256([]byte("foo(uint32)"))[:4]
|
||||
sig = append(sig, make([]byte, 32)...)
|
||||
sig[35] = 10
|
||||
|
||||
packed, err := abi.Pack("foo", uint32(10))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiPack(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
@ -292,77 +595,6 @@ func TestMultiPack(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackSlice(t *testing.T) {
|
||||
abi, err := JSON(strings.NewReader(jsondata2))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
sig := crypto.Keccak256([]byte("slice(uint32[2])"))[:4]
|
||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
|
||||
packed, err := abi.Pack("slice", []uint32{1, 2})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
var addrA, addrB = common.Address{1}, common.Address{2}
|
||||
sig = abi.Methods["sliceAddress"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||
|
||||
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
var addrC, addrD = common.Address{3}, common.Address{4}
|
||||
sig = abi.Methods["sliceMultiAddress"].Id()
|
||||
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||
|
||||
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
|
||||
sig = crypto.Keccak256([]byte("slice256(uint256[2])"))[:4]
|
||||
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||
|
||||
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(packed, sig) {
|
||||
t.Errorf("expected %x got %x", sig, packed)
|
||||
}
|
||||
}
|
||||
func ExampleJSON() {
|
||||
const definition = `[{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBar","outputs":[{"name":"","type":"bool"}],"type":"function"}]`
|
||||
|
||||
@ -382,9 +614,9 @@ func ExampleJSON() {
|
||||
|
||||
func TestInputVariableInputLength(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "type" : "function", "name" : "strOne", "const" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
||||
{ "type" : "function", "name" : "bytesOne", "const" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
||||
{ "type" : "function", "name" : "strTwo", "const" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] }
|
||||
{ "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] },
|
||||
{ "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] },
|
||||
{ "type" : "function", "name" : "strTwo", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "str1", "type" : "string" } ] }
|
||||
]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
@ -546,7 +778,7 @@ func TestBareEvents(t *testing.T) {
|
||||
|
||||
func TestMultiReturnWithStruct(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "name" : "multi", "const" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
@ -599,7 +831,7 @@ func TestMultiReturnWithStruct(t *testing.T) {
|
||||
|
||||
func TestMultiReturnWithSlice(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "name" : "multi", "const" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
@ -635,8 +867,8 @@ func TestMultiReturnWithSlice(t *testing.T) {
|
||||
|
||||
func TestMarshalArrays(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "name" : "bytes32", "const" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||
{ "name" : "bytes10", "const" : false, "outputs": [ { "type": "bytes10" } ] }
|
||||
{ "name" : "bytes32", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||
{ "name" : "bytes10", "constant" : false, "outputs": [ { "type": "bytes10" } ] }
|
||||
]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
@ -694,14 +926,14 @@ func TestMarshalArrays(t *testing.T) {
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
const definition = `[
|
||||
{ "name" : "int", "const" : false, "outputs": [ { "type": "uint256" } ] },
|
||||
{ "name" : "bool", "const" : false, "outputs": [ { "type": "bool" } ] },
|
||||
{ "name" : "bytes", "const" : false, "outputs": [ { "type": "bytes" } ] },
|
||||
{ "name" : "fixed", "const" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||
{ "name" : "multi", "const" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
|
||||
{ "name" : "addressSliceSingle", "const" : false, "outputs": [ { "type": "address[]" } ] },
|
||||
{ "name" : "addressSliceDouble", "const" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
|
||||
{ "name" : "mixedBytes", "const" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
|
||||
{ "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] },
|
||||
{ "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] },
|
||||
{ "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] },
|
||||
{ "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] },
|
||||
{ "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] },
|
||||
{ "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] },
|
||||
{ "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] },
|
||||
{ "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]`
|
||||
|
||||
abi, err := JSON(strings.NewReader(definition))
|
||||
if err != nil {
|
||||
|
@ -17,15 +17,26 @@
|
||||
package bind
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// ErrNoCode is returned by call and transact operations for which the requested
|
||||
// recipient contract to operate on does not exist in the state db or does not
|
||||
// have any code associated with it (i.e. suicided).
|
||||
var ErrNoCode = errors.New("no contract code at given address")
|
||||
|
||||
// ContractCaller defines the methods needed to allow operating with contract on a read
|
||||
// only basis.
|
||||
type ContractCaller interface {
|
||||
// HasCode checks if the contract at the given address has any code associated
|
||||
// with it or not. This is needed to differentiate between contract internal
|
||||
// errors and the local chain being out of sync.
|
||||
HasCode(contract common.Address, pending bool) (bool, error)
|
||||
|
||||
// ContractCall executes an Ethereum contract call with the specified data as
|
||||
// the input. The pending flag requests execution against the pending block, not
|
||||
// the stable head of the chain.
|
||||
@ -37,7 +48,51 @@ type ContractCaller interface {
|
||||
// used when the user does not provide some needed values, but rather leaves it up
|
||||
// to the transactor to decide.
|
||||
type ContractTransactor interface {
|
||||
// Nonce retrieves the current pending nonce associated with an account.
|
||||
// PendingAccountNonce retrieves the current pending nonce associated with an
|
||||
// account.
|
||||
PendingAccountNonce(account common.Address) (uint64, error)
|
||||
|
||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
||||
// execution of a transaction.
|
||||
SuggestGasPrice() (*big.Int, error)
|
||||
|
||||
// HasCode checks if the contract at the given address has any code associated
|
||||
// with it or not. This is needed to differentiate between contract internal
|
||||
// errors and the local chain being out of sync.
|
||||
HasCode(contract common.Address, pending bool) (bool, error)
|
||||
|
||||
// EstimateGasLimit tries to estimate the gas needed to execute a specific
|
||||
// transaction based on the current pending state of the backend blockchain.
|
||||
// There is no guarantee that this is the true gas limit requirement as other
|
||||
// transactions may be added or removed by miners, but it should provide a basis
|
||||
// for setting a reasonable default.
|
||||
EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error)
|
||||
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(tx *types.Transaction) error
|
||||
}
|
||||
|
||||
// ContractBackend defines the methods needed to allow operating with contract
|
||||
// on a read-write basis.
|
||||
//
|
||||
// This interface is essentially the union of ContractCaller and ContractTransactor
|
||||
// but due to a bug in the Go compiler (https://github.com/golang/go/issues/6977),
|
||||
// we cannot simply list it as the two interfaces. The other solution is to add a
|
||||
// third interface containing the common methods, but that convolutes the user API
|
||||
// as it introduces yet another parameter to require for initialization.
|
||||
type ContractBackend interface {
|
||||
// HasCode checks if the contract at the given address has any code associated
|
||||
// with it or not. This is needed to differentiate between contract internal
|
||||
// errors and the local chain being out of sync.
|
||||
HasCode(contract common.Address, pending bool) (bool, error)
|
||||
|
||||
// ContractCall executes an Ethereum contract call with the specified data as
|
||||
// the input. The pending flag requests execution against the pending block, not
|
||||
// the stable head of the chain.
|
||||
ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error)
|
||||
|
||||
// PendingAccountNonce retrieves the current pending nonce associated with an
|
||||
// account.
|
||||
PendingAccountNonce(account common.Address) (uint64, error)
|
||||
|
||||
// SuggestGasPrice retrieves the currently suggested gas price to allow a timely
|
||||
@ -52,12 +107,5 @@ type ContractTransactor interface {
|
||||
EstimateGasLimit(sender common.Address, contract *common.Address, value *big.Int, data []byte) (*big.Int, error)
|
||||
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(*types.Transaction) error
|
||||
}
|
||||
|
||||
// ContractBackend defines the methods needed to allow operating with contract
|
||||
// on a read-write basis.
|
||||
type ContractBackend interface {
|
||||
ContractCaller
|
||||
ContractTransactor
|
||||
SendTransaction(tx *types.Transaction) error
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ func (*nilBackend) ContractCall(common.Address, []byte, bool) ([]byte, error) {
|
||||
func (*nilBackend) EstimateGasLimit(common.Address, *common.Address, *big.Int, []byte) (*big.Int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (*nilBackend) HasCode(common.Address, bool) (bool, error) { panic("not implemented") }
|
||||
func (*nilBackend) SuggestGasPrice() (*big.Int, error) { panic("not implemented") }
|
||||
func (*nilBackend) PendingAccountNonce(common.Address) (uint64, error) { panic("not implemented") }
|
||||
func (*nilBackend) SendTransaction(*types.Transaction) error { panic("not implemented") }
|
||||
|
@ -66,10 +66,16 @@ type request struct {
|
||||
type response struct {
|
||||
JSONRPC string `json:"jsonrpc"` // Version of the JSON RPC protocol, always set to 2.0
|
||||
ID int `json:"id"` // Auto incrementing ID number for this request
|
||||
Error json.RawMessage `json:"error"` // Any error returned by the remote side
|
||||
Error *failure `json:"error"` // Any error returned by the remote side
|
||||
Result json.RawMessage `json:"result"` // Whatever the remote side sends us in reply
|
||||
}
|
||||
|
||||
// failure is a JSON RPC response error field sent back from the API server.
|
||||
type failure struct {
|
||||
Code int `json:"code"` // JSON RPC error code associated with the failure
|
||||
Message string `json:"message"` // Specific error message of the failure
|
||||
}
|
||||
|
||||
// request forwards an API request to the RPC server, and parses the response.
|
||||
//
|
||||
// This is currently painfully non-concurrent, but it will have to do until we
|
||||
@ -96,12 +102,35 @@ func (b *rpcBackend) request(method string, params []interface{}) (json.RawMessa
|
||||
if err := b.client.Recv(res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res.Error) > 0 {
|
||||
return nil, fmt.Errorf("remote error: %s", string(res.Error))
|
||||
if res.Error != nil {
|
||||
if res.Error.Message == bind.ErrNoCode.Error() {
|
||||
return nil, bind.ErrNoCode
|
||||
}
|
||||
return nil, fmt.Errorf("remote error: %s", res.Error.Message)
|
||||
}
|
||||
return res.Result, nil
|
||||
}
|
||||
|
||||
// HasCode implements ContractVerifier.HasCode by retrieving any code associated
|
||||
// with the contract from the remote node, and checking its size.
|
||||
func (b *rpcBackend) HasCode(contract common.Address, pending bool) (bool, error) {
|
||||
// Execute the RPC code retrieval
|
||||
block := "latest"
|
||||
if pending {
|
||||
block = "pending"
|
||||
}
|
||||
res, err := b.request("eth_getCode", []interface{}{contract.Hex(), block})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
var hex string
|
||||
if err := json.Unmarshal(res, &hex); err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Convert the response back to a Go byte slice and return
|
||||
return len(common.FromHex(hex)) > 0, nil
|
||||
}
|
||||
|
||||
// ContractCall implements ContractCaller.ContractCall, delegating the execution of
|
||||
// a contract call to the remote node, returning the reply to for local processing.
|
||||
func (b *rpcBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
|
||||
|
@ -72,12 +72,22 @@ func (b *SimulatedBackend) Commit() {
|
||||
|
||||
// Rollback aborts all pending transactions, reverting to the last committed state.
|
||||
func (b *SimulatedBackend) Rollback() {
|
||||
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
|
||||
blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
|
||||
|
||||
b.pendingBlock = blocks[0]
|
||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), b.database)
|
||||
}
|
||||
|
||||
// HasCode implements ContractVerifier.HasCode, checking whether there is any
|
||||
// code associated with a certain account in the blockchain.
|
||||
func (b *SimulatedBackend) HasCode(contract common.Address, pending bool) (bool, error) {
|
||||
if pending {
|
||||
return len(b.pendingState.GetCode(contract)) > 0, nil
|
||||
}
|
||||
statedb, _ := b.blockchain.State()
|
||||
return len(statedb.GetCode(contract)) > 0, nil
|
||||
}
|
||||
|
||||
// ContractCall implements ContractCaller.ContractCall, executing the specified
|
||||
// contract with the given input data.
|
||||
func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pending bool) ([]byte, error) {
|
||||
@ -92,6 +102,10 @@ func (b *SimulatedBackend) ContractCall(contract common.Address, data []byte, pe
|
||||
block = b.blockchain.CurrentBlock()
|
||||
statedb, _ = b.blockchain.State()
|
||||
}
|
||||
// If there's no code to interact with, respond with an appropriate error
|
||||
if code := statedb.GetCode(contract); len(code) == 0 {
|
||||
return nil, bind.ErrNoCode
|
||||
}
|
||||
// Set infinite balance to the a fake caller account
|
||||
from := statedb.GetOrNewStateObject(common.Address{})
|
||||
from.SetBalance(common.MaxBig)
|
||||
@ -134,7 +148,12 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
|
||||
block = b.pendingBlock
|
||||
statedb = b.pendingState.Copy()
|
||||
)
|
||||
|
||||
// If there's no code to interact with, respond with an appropriate error
|
||||
if contract != nil {
|
||||
if code := statedb.GetCode(*contract); len(code) == 0 {
|
||||
return nil, bind.ErrNoCode
|
||||
}
|
||||
}
|
||||
// Set infinite balance to the a fake caller account
|
||||
from := statedb.GetOrNewStateObject(sender)
|
||||
from.SetBalance(common.MaxBig)
|
||||
@ -159,7 +178,7 @@ func (b *SimulatedBackend) EstimateGasLimit(sender common.Address, contract *com
|
||||
// SendTransaction implements ContractTransactor.SendTransaction, delegating the raw
|
||||
// transaction injection to the remote node.
|
||||
func (b *SimulatedBackend) SendTransaction(tx *types.Transaction) error {
|
||||
blocks, _ := core.GenerateChain(b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
blocks, _ := core.GenerateChain(nil, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||
for _, tx := range b.pendingBlock.Transactions() {
|
||||
block.AddTx(tx)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -56,6 +57,9 @@ 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
|
||||
|
||||
latestHasCode uint32 // Cached verification that the latest state contains code for this contract
|
||||
pendingHasCode uint32 // Cached verification that the pending state contains code for this contract
|
||||
}
|
||||
|
||||
// NewBoundContract creates a low level contract interface through which calls
|
||||
@ -96,6 +100,19 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
|
||||
if opts == nil {
|
||||
opts = new(CallOpts)
|
||||
}
|
||||
// Make sure we have a contract to operate on, and bail out otherwise
|
||||
if (opts.Pending && atomic.LoadUint32(&c.pendingHasCode) == 0) || (!opts.Pending && atomic.LoadUint32(&c.latestHasCode) == 0) {
|
||||
if code, err := c.caller.HasCode(c.address, opts.Pending); err != nil {
|
||||
return err
|
||||
} else if !code {
|
||||
return ErrNoCode
|
||||
}
|
||||
if opts.Pending {
|
||||
atomic.StoreUint32(&c.pendingHasCode, 1)
|
||||
} else {
|
||||
atomic.StoreUint32(&c.latestHasCode, 1)
|
||||
}
|
||||
}
|
||||
// Pack the input, call and unpack the results
|
||||
input, err := c.abi.Pack(method, params...)
|
||||
if err != nil {
|
||||
@ -153,6 +170,16 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
||||
}
|
||||
gasLimit := opts.GasLimit
|
||||
if gasLimit == nil {
|
||||
// Gas estimation cannot succeed without code for method invocations
|
||||
if contract != nil && atomic.LoadUint32(&c.pendingHasCode) == 0 {
|
||||
if code, err := c.transactor.HasCode(c.address, true); err != nil {
|
||||
return nil, err
|
||||
} else if !code {
|
||||
return nil, ErrNoCode
|
||||
}
|
||||
atomic.StoreUint32(&c.pendingHasCode, 1)
|
||||
}
|
||||
// If the contract surely has code (or code is not needed), estimate the transaction
|
||||
gasLimit, err = c.transactor.EstimateGasLimit(opts.From, contract, value, input)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to exstimate gas needed: %v", err)
|
||||
|
@ -194,12 +194,44 @@ var bindTests = []struct {
|
||||
}
|
||||
`,
|
||||
},
|
||||
// Tests that plain values can be properly returned and deserialized
|
||||
{
|
||||
`Getter`,
|
||||
`
|
||||
contract Getter {
|
||||
function getter() constant returns (string, int, bytes32) {
|
||||
return ("Hi", 1, sha3(""));
|
||||
}
|
||||
}
|
||||
`,
|
||||
`606060405260dc8060106000396000f3606060405260e060020a6000350463993a04b78114601a575b005b600060605260c0604052600260809081527f486900000000000000000000000000000000000000000000000000000000000060a05260017fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47060e0829052610100819052606060c0908152600261012081905281906101409060a09080838184600060046012f1505081517fffff000000000000000000000000000000000000000000000000000000000000169091525050604051610160819003945092505050f3`,
|
||||
`[{"constant":true,"inputs":[],"name":"getter","outputs":[{"name":"","type":"string"},{"name":"","type":"int256"},{"name":"","type":"bytes32"}],"type":"function"}]`,
|
||||
`
|
||||
// Generate a new random account and a funded simulator
|
||||
key, _ := crypto.GenerateKey()
|
||||
auth := bind.NewKeyedTransactor(key)
|
||||
sim := backends.NewSimulatedBackend(core.GenesisAccount{Address: auth.From, Balance: big.NewInt(10000000000)})
|
||||
|
||||
// Deploy a tuple tester contract and execute a structured call on it
|
||||
_, _, getter, err := DeployGetter(auth, sim)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to deploy getter contract: %v", err)
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
if str, num, _, err := getter.Getter(nil); err != nil {
|
||||
t.Fatalf("Failed to call anonymous field retriever: %v", err)
|
||||
} else if str != "Hi" || num.Cmp(big.NewInt(1)) != 0 {
|
||||
t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", str, num, "Hi", 1)
|
||||
}
|
||||
`,
|
||||
},
|
||||
// Tests that tuples can be properly returned and deserialized
|
||||
{
|
||||
`Tupler`,
|
||||
`
|
||||
contract Tupler {
|
||||
function tuple() returns (string a, int b, bytes32 c) {
|
||||
function tuple() constant returns (string a, int b, bytes32 c) {
|
||||
return ("Hi", 1, sha3(""));
|
||||
}
|
||||
}
|
||||
@ -219,8 +251,10 @@ var bindTests = []struct {
|
||||
}
|
||||
sim.Commit()
|
||||
|
||||
if _, err := tupler.Tuple(nil); err != nil {
|
||||
if res, err := tupler.Tuple(nil); err != nil {
|
||||
t.Fatalf("Failed to call structure retriever: %v", err)
|
||||
} else if res.A != "Hi" || res.B.Cmp(big.NewInt(1)) != 0 {
|
||||
t.Fatalf("Retrieved value mismatch: have %v/%v, want %v/%v", res.A, res.B, "Hi", 1)
|
||||
}
|
||||
`,
|
||||
},
|
||||
@ -303,6 +337,34 @@ var bindTests = []struct {
|
||||
}
|
||||
`,
|
||||
},
|
||||
// Tests that non-existent contracts are reported as such (though only simulator test)
|
||||
{
|
||||
`NonExistent`,
|
||||
`
|
||||
contract NonExistent {
|
||||
function String() constant returns(string) {
|
||||
return "I don't exist";
|
||||
}
|
||||
}
|
||||
`,
|
||||
`6060604052609f8060106000396000f3606060405260e060020a6000350463f97a60058114601a575b005b600060605260c0604052600d60809081527f4920646f6e27742065786973740000000000000000000000000000000000000060a052602060c0908152600d60e081905281906101009060a09080838184600060046012f15050815172ffffffffffffffffffffffffffffffffffffff1916909152505060405161012081900392509050f3`,
|
||||
`[{"constant":true,"inputs":[],"name":"String","outputs":[{"name":"","type":"string"}],"type":"function"}]`,
|
||||
`
|
||||
// Create a simulator and wrap a non-deployed contract
|
||||
sim := backends.NewSimulatedBackend()
|
||||
|
||||
nonexistent, err := NewNonExistent(common.Address{}, sim)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to access non-existent contract: %v", err)
|
||||
}
|
||||
// Ensure that contract calls fail with the appropriate error
|
||||
if res, err := nonexistent.String(nil); err == nil {
|
||||
t.Fatalf("Call succeeded on non-existent contract: %v", res)
|
||||
} else if (err != bind.ErrNoCode) {
|
||||
t.Fatalf("Error mismatch: have %v, want %v", err, bind.ErrNoCode)
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
// Tests that packages generated by the binder can be successfully compiled and
|
||||
|
@ -211,7 +211,7 @@ package {{.Package}}
|
||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type}})
|
||||
{{end}}
|
||||
){{end}}
|
||||
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}[]interface{}{
|
||||
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{
|
||||
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}},
|
||||
{{end}}
|
||||
}{{end}}{{end}}
|
||||
|
79
accounts/abi/error.go
Normal file
79
accounts/abi/error.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// formatSliceString formats the reflection kind with the given slice size
|
||||
// and returns a formatted string representation.
|
||||
func formatSliceString(kind reflect.Kind, sliceSize int) string {
|
||||
if sliceSize == -1 {
|
||||
return fmt.Sprintf("[]%v", kind)
|
||||
}
|
||||
return fmt.Sprintf("[%d]%v", sliceSize, kind)
|
||||
}
|
||||
|
||||
// sliceTypeCheck checks that the given slice can by assigned to the reflection
|
||||
// type in t.
|
||||
func sliceTypeCheck(t Type, val reflect.Value) error {
|
||||
if val.Kind() != reflect.Slice && val.Kind() != reflect.Array {
|
||||
return typeErr(formatSliceString(t.Kind, t.SliceSize), val.Type())
|
||||
}
|
||||
if t.IsArray && val.Len() != t.SliceSize {
|
||||
return typeErr(formatSliceString(t.Elem.Kind, t.SliceSize), formatSliceString(val.Type().Elem().Kind(), val.Len()))
|
||||
}
|
||||
|
||||
if t.Elem.IsSlice {
|
||||
if val.Len() > 0 {
|
||||
return sliceTypeCheck(*t.Elem, val.Index(0))
|
||||
}
|
||||
} else if t.Elem.IsArray {
|
||||
return sliceTypeCheck(*t.Elem, val.Index(0))
|
||||
}
|
||||
|
||||
if elemKind := val.Type().Elem().Kind(); elemKind != t.Elem.Kind {
|
||||
return typeErr(formatSliceString(t.Elem.Kind, t.SliceSize), val.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// typeCheck checks that the given reflection value can be assigned to the reflection
|
||||
// type in t.
|
||||
func typeCheck(t Type, value reflect.Value) error {
|
||||
if t.IsSlice || t.IsArray {
|
||||
return sliceTypeCheck(t, value)
|
||||
}
|
||||
|
||||
// Check base type validity. Element types will be checked later on.
|
||||
if t.Kind != value.Kind() {
|
||||
return typeErr(t.Kind, value.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// varErr returns a formatted error.
|
||||
func varErr(expected, got reflect.Kind) error {
|
||||
return typeErr(expected, got)
|
||||
}
|
||||
|
||||
// typeErr returns a formatted type casting error.
|
||||
func typeErr(expected, got interface{}) error {
|
||||
return fmt.Errorf("abi: cannot use %v as type %v as argument", got, expected)
|
||||
}
|
@ -18,6 +18,7 @@ package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
@ -38,6 +39,44 @@ type Method struct {
|
||||
Outputs []Argument
|
||||
}
|
||||
|
||||
func (m Method) pack(method Method, 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
|
||||
}
|
||||
|
||||
// Sig returns the methods string signature according to the ABI spec.
|
||||
//
|
||||
// Example
|
||||
|
@ -24,8 +24,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
big_t = reflect.TypeOf(&big.Int{})
|
||||
ubig_t = reflect.TypeOf(&big.Int{})
|
||||
big_t = reflect.TypeOf(big.Int{})
|
||||
ubig_t = reflect.TypeOf(big.Int{})
|
||||
byte_t = reflect.TypeOf(byte(0))
|
||||
byte_ts = reflect.TypeOf([]byte(nil))
|
||||
uint_t = reflect.TypeOf(uint(0))
|
||||
@ -56,61 +56,21 @@ var (
|
||||
big_ts = reflect.TypeOf([]*big.Int(nil))
|
||||
)
|
||||
|
||||
// U256 will ensure unsigned 256bit on big nums
|
||||
// U256 converts a big Int into a 256bit EVM number.
|
||||
func U256(n *big.Int) []byte {
|
||||
return common.LeftPadBytes(common.U256(n).Bytes(), 32)
|
||||
}
|
||||
|
||||
func S256(n *big.Int) []byte {
|
||||
sint := common.S256(n)
|
||||
ret := common.LeftPadBytes(sint.Bytes(), 32)
|
||||
if sint.Cmp(common.Big0) < 0 {
|
||||
for i, b := range ret {
|
||||
if b == 0 {
|
||||
ret[i] = 1
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// S256 will ensure signed 256bit on big nums
|
||||
func U2U256(n uint64) []byte {
|
||||
return U256(big.NewInt(int64(n)))
|
||||
}
|
||||
|
||||
func S2S256(n int64) []byte {
|
||||
return S256(big.NewInt(n))
|
||||
}
|
||||
|
||||
// packNum packs the given number (using the reflect value) and will cast it to appropriate number representation
|
||||
func packNum(value reflect.Value, to byte) []byte {
|
||||
func packNum(value reflect.Value) []byte {
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if to == UintTy {
|
||||
return U2U256(value.Uint())
|
||||
} else {
|
||||
return S2S256(int64(value.Uint()))
|
||||
}
|
||||
return U256(new(big.Int).SetUint64(value.Uint()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if to == UintTy {
|
||||
return U2U256(uint64(value.Int()))
|
||||
} else {
|
||||
return S2S256(value.Int())
|
||||
}
|
||||
return U256(big.NewInt(value.Int()))
|
||||
case reflect.Ptr:
|
||||
// This only takes care of packing and casting. No type checking is done here. It should be done prior to using this function.
|
||||
if to == UintTy {
|
||||
return U256(value.Interface().(*big.Int))
|
||||
} else {
|
||||
return S256(value.Interface().(*big.Int))
|
||||
}
|
||||
|
||||
return U256(value.Interface().(*big.Int))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package abi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -26,49 +27,46 @@ import (
|
||||
func TestNumberTypes(t *testing.T) {
|
||||
ubytes := make([]byte, 32)
|
||||
ubytes[31] = 1
|
||||
sbytesmin := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
|
||||
unsigned := U256(big.NewInt(1))
|
||||
if !bytes.Equal(unsigned, ubytes) {
|
||||
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||
}
|
||||
|
||||
signed := S256(big.NewInt(1))
|
||||
if !bytes.Equal(signed, ubytes) {
|
||||
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||
}
|
||||
|
||||
signed = S256(big.NewInt(-1))
|
||||
if !bytes.Equal(signed, sbytesmin) {
|
||||
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPackNumber(t *testing.T) {
|
||||
ubytes := make([]byte, 32)
|
||||
ubytes[31] = 1
|
||||
sbytesmin := []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
||||
maxunsigned := []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
|
||||
tests := []struct {
|
||||
value reflect.Value
|
||||
packed []byte
|
||||
}{
|
||||
// Protocol limits
|
||||
{reflect.ValueOf(0), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
{reflect.ValueOf(1), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
{reflect.ValueOf(-1), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}},
|
||||
|
||||
packed := packNum(reflect.ValueOf(1), IntTy)
|
||||
if !bytes.Equal(packed, ubytes) {
|
||||
t.Errorf("expected %x got %x", ubytes, packed)
|
||||
}
|
||||
packed = packNum(reflect.ValueOf(-1), IntTy)
|
||||
if !bytes.Equal(packed, sbytesmin) {
|
||||
t.Errorf("expected %x got %x", ubytes, packed)
|
||||
}
|
||||
packed = packNum(reflect.ValueOf(1), UintTy)
|
||||
if !bytes.Equal(packed, ubytes) {
|
||||
t.Errorf("expected %x got %x", ubytes, packed)
|
||||
}
|
||||
packed = packNum(reflect.ValueOf(-1), UintTy)
|
||||
if !bytes.Equal(packed, maxunsigned) {
|
||||
t.Errorf("expected %x got %x", maxunsigned, packed)
|
||||
}
|
||||
// Type corner cases
|
||||
{reflect.ValueOf(uint8(math.MaxUint8)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255}},
|
||||
{reflect.ValueOf(uint16(math.MaxUint16)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255}},
|
||||
{reflect.ValueOf(uint32(math.MaxUint32)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255}},
|
||||
{reflect.ValueOf(uint64(math.MaxUint64)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255}},
|
||||
|
||||
packed = packNum(reflect.ValueOf("string"), UintTy)
|
||||
if packed != nil {
|
||||
{reflect.ValueOf(int8(math.MaxInt8)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127}},
|
||||
{reflect.ValueOf(int16(math.MaxInt16)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255}},
|
||||
{reflect.ValueOf(int32(math.MaxInt32)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255, 255, 255}},
|
||||
{reflect.ValueOf(int64(math.MaxInt64)), []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 127, 255, 255, 255, 255, 255, 255, 255}},
|
||||
|
||||
{reflect.ValueOf(int8(math.MinInt8)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128}},
|
||||
{reflect.ValueOf(int16(math.MinInt16)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 0}},
|
||||
{reflect.ValueOf(int32(math.MinInt32)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 0, 0, 0}},
|
||||
{reflect.ValueOf(int64(math.MinInt64)), []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 0, 0, 0, 0, 0, 0, 0}},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
packed := packNum(tt.value)
|
||||
if !bytes.Equal(packed, tt.packed) {
|
||||
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
||||
}
|
||||
}
|
||||
if packed := packNum(reflect.ValueOf("string")); packed != nil {
|
||||
t.Errorf("expected 'string' to pack to nil. got %x instead", packed)
|
||||
}
|
||||
}
|
||||
|
65
accounts/abi/packing.go
Normal file
65
accounts/abi/packing.go
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
|
||||
// bytes slice
|
||||
func packBytesSlice(bytes []byte, l int) []byte {
|
||||
len := packNum(reflect.ValueOf(l))
|
||||
return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...)
|
||||
}
|
||||
|
||||
// packElement packs the given reflect value according to the abi specification in
|
||||
// t.
|
||||
func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||
switch t.T {
|
||||
case IntTy, UintTy:
|
||||
return packNum(reflectValue)
|
||||
case StringTy:
|
||||
return packBytesSlice([]byte(reflectValue.String()), reflectValue.Len())
|
||||
case AddressTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
}
|
||||
|
||||
return common.LeftPadBytes(reflectValue.Bytes(), 32)
|
||||
case BoolTy:
|
||||
if reflectValue.Bool() {
|
||||
return common.LeftPadBytes(common.Big1.Bytes(), 32)
|
||||
} else {
|
||||
return common.LeftPadBytes(common.Big0.Bytes(), 32)
|
||||
}
|
||||
case BytesTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
}
|
||||
return packBytesSlice(reflectValue.Bytes(), reflectValue.Len())
|
||||
case FixedBytesTy:
|
||||
if reflectValue.Kind() == reflect.Array {
|
||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||
}
|
||||
|
||||
return common.RightPadBytes(reflectValue.Bytes(), 32)
|
||||
}
|
||||
panic("abi: fatal error")
|
||||
}
|
97
accounts/abi/reflect.go
Normal file
97
accounts/abi/reflect.go
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package abi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// indirect recursively dereferences the value until it either gets the value
|
||||
// or finds a big.Int
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Ptr && v.Elem().Type() != big_t {
|
||||
return indirect(v.Elem())
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// reflectIntKind returns the reflect using the given size and
|
||||
// unsignedness.
|
||||
func reflectIntKind(unsigned bool, size int) reflect.Kind {
|
||||
switch size {
|
||||
case 8:
|
||||
if unsigned {
|
||||
return reflect.Uint8
|
||||
}
|
||||
return reflect.Int8
|
||||
case 16:
|
||||
if unsigned {
|
||||
return reflect.Uint16
|
||||
}
|
||||
return reflect.Int16
|
||||
case 32:
|
||||
if unsigned {
|
||||
return reflect.Uint32
|
||||
}
|
||||
return reflect.Int32
|
||||
case 64:
|
||||
if unsigned {
|
||||
return reflect.Uint64
|
||||
}
|
||||
return reflect.Int64
|
||||
}
|
||||
return reflect.Ptr
|
||||
}
|
||||
|
||||
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
|
||||
// and copies the bytes in value to the new slice.
|
||||
func mustArrayToByteSlice(value reflect.Value) reflect.Value {
|
||||
slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len())
|
||||
reflect.Copy(slice, value)
|
||||
return slice
|
||||
}
|
||||
|
||||
// set attempts to assign src to dst by either setting, copying or otherwise.
|
||||
//
|
||||
// set is a bit more lenient when it comes to assignment and doesn't force an as
|
||||
// strict ruleset as bare `reflect` does.
|
||||
func set(dst, src reflect.Value, output Argument) error {
|
||||
dstType := dst.Type()
|
||||
srcType := src.Type()
|
||||
|
||||
switch {
|
||||
case dstType.AssignableTo(src.Type()):
|
||||
dst.Set(src)
|
||||
case dstType.Kind() == reflect.Array && srcType.Kind() == reflect.Slice:
|
||||
if !dstType.Elem().AssignableTo(r_byte) {
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to array of elem %v", src.Type(), dstType.Elem())
|
||||
}
|
||||
|
||||
if dst.Len() < output.Type.SliceSize {
|
||||
return fmt.Errorf("abi: cannot unmarshal src (len=%d) in to dst (len=%d)", output.Type.SliceSize, dst.Len())
|
||||
}
|
||||
reflect.Copy(dst, src)
|
||||
case dstType.Kind() == reflect.Interface:
|
||||
dst.Set(src)
|
||||
case dstType.Kind() == reflect.Ptr:
|
||||
return set(dst.Elem(), src, output)
|
||||
default:
|
||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
@ -21,8 +21,6 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -40,53 +38,60 @@ const (
|
||||
|
||||
// Type is the reflection of the supported argument type
|
||||
type Type struct {
|
||||
IsSlice bool
|
||||
SliceSize int
|
||||
IsSlice, IsArray bool
|
||||
SliceSize int
|
||||
|
||||
Elem *Type
|
||||
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Size int
|
||||
T byte // Our own type checking
|
||||
|
||||
Kind reflect.Kind
|
||||
Type reflect.Type
|
||||
Size int
|
||||
T byte // Our own type checking
|
||||
stringKind string // holds the unparsed string for deriving signatures
|
||||
}
|
||||
|
||||
var (
|
||||
// fullTypeRegex parses the abi types
|
||||
//
|
||||
// Types can be in the format of:
|
||||
//
|
||||
// Input = Type [ "[" [ Number ] "]" ] Name .
|
||||
// Type = [ "u" ] "int" [ Number ] .
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// string int uint real
|
||||
// string32 int8 uint8 uint[]
|
||||
// address int256 uint256 real[2]
|
||||
fullTypeRegex = regexp.MustCompile("([a-zA-Z0-9]+)(\\[([0-9]*)?\\])?")
|
||||
typeRegex = regexp.MustCompile("([a-zA-Z]+)([0-9]*)?")
|
||||
// typeRegex parses the abi sub types
|
||||
typeRegex = regexp.MustCompile("([a-zA-Z]+)([0-9]*)?")
|
||||
)
|
||||
|
||||
// NewType returns a fully parsed Type given by the input string or an error if it can't be parsed.
|
||||
//
|
||||
// Strings can be in the format of:
|
||||
//
|
||||
// Input = Type [ "[" [ Number ] "]" ] Name .
|
||||
// Type = [ "u" ] "int" [ Number ] .
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// string int uint real
|
||||
// string32 int8 uint8 uint[]
|
||||
// address int256 uint256 real[2]
|
||||
// NewType creates a new reflection type of abi type given in t.
|
||||
func NewType(t string) (typ Type, err error) {
|
||||
// 1. full string 2. type 3. (opt.) is slice 4. (opt.) size
|
||||
// parse the full representation of the abi-type definition; including:
|
||||
// * full string
|
||||
// * type
|
||||
// * is slice
|
||||
// * slice size
|
||||
res := fullTypeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||
|
||||
// check if type is slice and parse type.
|
||||
switch {
|
||||
case res[3] != "":
|
||||
// err is ignored. Already checked for number through the regexp
|
||||
typ.SliceSize, _ = strconv.Atoi(res[3])
|
||||
typ.IsSlice = true
|
||||
typ.IsArray = true
|
||||
case res[2] != "":
|
||||
typ.IsSlice, typ.SliceSize = true, -1
|
||||
case res[0] == "":
|
||||
return Type{}, fmt.Errorf("abi: type parse error: %s", t)
|
||||
}
|
||||
if typ.IsArray || typ.IsSlice {
|
||||
sliceType, err := NewType(res[1])
|
||||
if err != nil {
|
||||
return Type{}, err
|
||||
}
|
||||
typ.Elem = &sliceType
|
||||
typ.stringKind = sliceType.stringKind + t[len(res[1]):]
|
||||
return typ, nil
|
||||
}
|
||||
|
||||
// parse the type and size of the abi-type.
|
||||
parsedType := typeRegex.FindAllStringSubmatch(res[1], -1)[0]
|
||||
@ -106,24 +111,24 @@ func NewType(t string) (typ Type, err error) {
|
||||
varSize = 256
|
||||
t += "256"
|
||||
}
|
||||
typ.stringKind = t
|
||||
|
||||
switch varType {
|
||||
case "int":
|
||||
typ.Kind = reflect.Int
|
||||
typ.Kind = reflectIntKind(false, varSize)
|
||||
typ.Type = big_t
|
||||
typ.Size = varSize
|
||||
typ.T = IntTy
|
||||
case "uint":
|
||||
typ.Kind = reflect.Uint
|
||||
typ.Kind = reflectIntKind(true, varSize)
|
||||
typ.Type = ubig_t
|
||||
typ.Size = varSize
|
||||
typ.T = UintTy
|
||||
case "bool":
|
||||
typ.Kind = reflect.Bool
|
||||
typ.T = BoolTy
|
||||
case "real": // TODO
|
||||
typ.Kind = reflect.Invalid
|
||||
case "address":
|
||||
typ.Kind = reflect.Array
|
||||
typ.Type = address_t
|
||||
typ.Size = 20
|
||||
typ.T = AddressTy
|
||||
@ -131,123 +136,55 @@ func NewType(t string) (typ Type, err error) {
|
||||
typ.Kind = reflect.String
|
||||
typ.Size = -1
|
||||
typ.T = StringTy
|
||||
if varSize > 0 {
|
||||
typ.Size = 32
|
||||
}
|
||||
case "hash":
|
||||
typ.Kind = reflect.Array
|
||||
typ.Size = 32
|
||||
typ.Type = hash_t
|
||||
typ.T = HashTy
|
||||
case "bytes":
|
||||
typ.Kind = reflect.Array
|
||||
typ.Type = byte_ts
|
||||
typ.Size = varSize
|
||||
sliceType, _ := NewType("uint8")
|
||||
typ.Elem = &sliceType
|
||||
if varSize == 0 {
|
||||
typ.IsSlice = true
|
||||
typ.T = BytesTy
|
||||
typ.SliceSize = -1
|
||||
} else {
|
||||
typ.IsArray = true
|
||||
typ.T = FixedBytesTy
|
||||
typ.SliceSize = varSize
|
||||
}
|
||||
default:
|
||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||
}
|
||||
typ.stringKind = t
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// String implements Stringer
|
||||
func (t Type) String() (out string) {
|
||||
return t.stringKind
|
||||
}
|
||||
|
||||
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
|
||||
// bytes slice
|
||||
func packBytesSlice(bytes []byte, l int) []byte {
|
||||
len := packNum(reflect.ValueOf(l), UintTy)
|
||||
return append(len, common.RightPadBytes(bytes, (l+31)/32*32)...)
|
||||
}
|
||||
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||
// dereference pointer first if it's a pointer
|
||||
v = indirect(v)
|
||||
|
||||
// Test the given input parameter `v` and checks if it matches certain
|
||||
// criteria
|
||||
// * Big integers are checks for ptr types and if the given value is
|
||||
// assignable
|
||||
// * Integer are checked for size
|
||||
// * Strings, addresses and bytes are checks for type and size
|
||||
func (t Type) pack(v interface{}) ([]byte, error) {
|
||||
value := reflect.ValueOf(v)
|
||||
switch kind := value.Kind(); kind {
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
// check input is unsigned
|
||||
if t.Type != ubig_t {
|
||||
return nil, fmt.Errorf("abi: type mismatch: %s for %T", t.Type, v)
|
||||
}
|
||||
|
||||
// no implicit type casting
|
||||
if int(value.Type().Size()*8) != t.Size {
|
||||
return nil, fmt.Errorf("abi: cannot use type %T as type uint%d", v, t.Size)
|
||||
}
|
||||
|
||||
return packNum(value, t.T), nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if t.Type != ubig_t {
|
||||
return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
|
||||
}
|
||||
|
||||
// no implicit type casting
|
||||
if int(value.Type().Size()*8) != t.Size {
|
||||
return nil, fmt.Errorf("abi: cannot use type %T as type uint%d", v, t.Size)
|
||||
}
|
||||
return packNum(value, t.T), nil
|
||||
case reflect.Ptr:
|
||||
// If the value is a ptr do a assign check (only used by
|
||||
// big.Int for now)
|
||||
if t.Type == ubig_t && value.Type() != ubig_t {
|
||||
return nil, fmt.Errorf("type mismatch: %s for %T", t.Type, v)
|
||||
}
|
||||
return packNum(value, t.T), nil
|
||||
case reflect.String:
|
||||
if t.Size > -1 && value.Len() > t.Size {
|
||||
return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
|
||||
}
|
||||
|
||||
return packBytesSlice([]byte(value.String()), value.Len()), nil
|
||||
case reflect.Slice:
|
||||
// Byte slice is a special case, it gets treated as a single value
|
||||
if t.T == BytesTy {
|
||||
return packBytesSlice(value.Bytes(), value.Len()), nil
|
||||
}
|
||||
|
||||
if t.SliceSize > -1 && value.Len() > t.SliceSize {
|
||||
return nil, fmt.Errorf("%v out of bound. %d for %d", value.Kind(), value.Len(), t.Size)
|
||||
}
|
||||
|
||||
// Signed / Unsigned check
|
||||
if value.Type() == big_t && (t.T != IntTy && isSigned(value)) || (t.T == UintTy && isSigned(value)) {
|
||||
return nil, fmt.Errorf("slice of incompatible types.")
|
||||
}
|
||||
if err := typeCheck(t, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if (t.IsSlice || t.IsArray) && t.T != BytesTy && t.T != FixedBytesTy {
|
||||
var packed []byte
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
val, err := t.pack(value.Index(i).Interface())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
val, err := t.Elem.pack(v.Index(i))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packed = append(packed, val...)
|
||||
}
|
||||
return packBytesSlice(packed, value.Len()), nil
|
||||
case reflect.Bool:
|
||||
if value.Bool() {
|
||||
return common.LeftPadBytes(common.Big1.Bytes(), 32), nil
|
||||
} else {
|
||||
return common.LeftPadBytes(common.Big0.Bytes(), 32), nil
|
||||
}
|
||||
case reflect.Array:
|
||||
if v, ok := value.Interface().(common.Address); ok {
|
||||
return common.LeftPadBytes(v[:], 32), nil
|
||||
} else if v, ok := value.Interface().(common.Hash); ok {
|
||||
return v[:], nil
|
||||
}
|
||||
return packBytesSlice(packed, v.Len()), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("ABI: bad input given %v", value.Kind())
|
||||
return packElement(t, v), nil
|
||||
}
|
||||
|
||||
// requireLengthPrefix returns whether the type requires any sort of length
|
||||
// prefixing.
|
||||
func (t Type) requiresLengthPrefix() bool {
|
||||
return t.T != FixedBytesTy && (t.T == StringTy || t.T == BytesTy || t.IsSlice || t.IsArray)
|
||||
}
|
||||
|
@ -147,9 +147,21 @@ func (am *Manager) Sign(addr common.Address, hash []byte) (signature []byte, err
|
||||
return crypto.Sign(hash, unlockedKey.PrivateKey)
|
||||
}
|
||||
|
||||
// SignWithPassphrase signs hash if the private key matching the given address can be
|
||||
// decrypted with the given passphrase.
|
||||
func (am *Manager) SignWithPassphrase(addr common.Address, passphrase string, hash []byte) (signature []byte, err error) {
|
||||
_, key, err := am.getDecryptedKey(Account{Address: addr}, passphrase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer zeroKey(key.PrivateKey)
|
||||
return crypto.Sign(hash, key.PrivateKey)
|
||||
}
|
||||
|
||||
// Unlock unlocks the given account indefinitely.
|
||||
func (am *Manager) Unlock(a Account, keyAuth string) error {
|
||||
return am.TimedUnlock(a, keyAuth, 0)
|
||||
func (am *Manager) Unlock(a Account, passphrase string) error {
|
||||
return am.TimedUnlock(a, passphrase, 0)
|
||||
}
|
||||
|
||||
// Lock removes the private key with the given address from memory.
|
||||
@ -284,7 +296,12 @@ func (am *Manager) Import(keyJSON []byte, passphrase, newPassphrase string) (Acc
|
||||
|
||||
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
||||
func (am *Manager) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (Account, error) {
|
||||
return am.importKey(newKeyFromECDSA(priv), passphrase)
|
||||
key := newKeyFromECDSA(priv)
|
||||
if am.cache.hasAddress(key.Address) {
|
||||
return Account{}, fmt.Errorf("account already exists")
|
||||
}
|
||||
|
||||
return am.importKey(key, passphrase)
|
||||
}
|
||||
|
||||
func (am *Manager) importKey(key *Key, passphrase string) (Account, error) {
|
||||
|
@ -81,6 +81,34 @@ func TestSign(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignWithPassphrase(t *testing.T) {
|
||||
dir, am := tmpManager(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
pass := "passwd"
|
||||
acc, err := am.NewAccount(pass)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, unlocked := am.unlocked[acc.Address]; unlocked {
|
||||
t.Fatal("expected account to be locked")
|
||||
}
|
||||
|
||||
_, err = am.SignWithPassphrase(acc.Address, pass, testSigData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, unlocked := am.unlocked[acc.Address]; unlocked {
|
||||
t.Fatal("expected account to be locked")
|
||||
}
|
||||
|
||||
if _, err = am.SignWithPassphrase(acc.Address, "invalid passwd", testSigData); err == nil {
|
||||
t.Fatal("expected SignHash to fail with invalid password")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimedUnlock(t *testing.T) {
|
||||
dir, am := tmpManager(t, true)
|
||||
defer os.RemoveAll(dir)
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin,!ios freebsd linux netbsd solaris windows
|
||||
// +build darwin,!ios freebsd linux,!arm64 netbsd solaris windows
|
||||
|
||||
package accounts
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build ios !darwin,!freebsd,!linux,!netbsd,!solaris,!windows
|
||||
// +build ios linux,arm64 !darwin,!freebsd,!linux,!netbsd,!solaris,!windows
|
||||
|
||||
// This is the fallback implementation of directory watching.
|
||||
// It is used on unsupported platforms.
|
||||
|
31
appveyor.yml
Normal file
31
appveyor.yml
Normal file
@ -0,0 +1,31 @@
|
||||
os: Visual Studio 2015
|
||||
|
||||
# Clone directly into GOPATH.
|
||||
clone_folder: c:\gopath\src\github.com\ethereum\go-ethereum
|
||||
clone_depth: 5
|
||||
version: "{branch}.{build}"
|
||||
environment:
|
||||
global:
|
||||
GOPATH: c:\gopath
|
||||
|
||||
# cache choco package files so we don't hit sourceforge all
|
||||
# the time.
|
||||
cache:
|
||||
- c:\cache
|
||||
|
||||
install:
|
||||
- cmd: choco install --cache c:\cache golang mingw | find /v "Extracting "
|
||||
- refreshenv
|
||||
- cd c:\gopath\src\github.com\ethereum\go-ethereum
|
||||
|
||||
build_script:
|
||||
- go run build\ci.go install
|
||||
|
||||
test_script:
|
||||
- go run build\ci.go test -vet -coverage
|
||||
|
||||
after_build:
|
||||
- go run build\ci.go archive -type zip
|
||||
|
||||
artifacts:
|
||||
- path: geth-*.zip
|
26
build/ci-notes.md
Normal file
26
build/ci-notes.md
Normal file
@ -0,0 +1,26 @@
|
||||
Debian Packaging
|
||||
----------------
|
||||
|
||||
Tagged releases and develop branch commits are available as installable Debian packages
|
||||
for Ubuntu. Packages are built for the all Ubuntu versions which are supported by
|
||||
Canonical:
|
||||
|
||||
- Trusty Tahr (14.04 LTS)
|
||||
- Wily Werewolf (15.10)
|
||||
- Xenial Xerus (16.04 LTS)
|
||||
|
||||
Packages of develop branch commits have suffix -unstable and cannot be installed alongside
|
||||
the stable version. Switching between release streams requires user intervention.
|
||||
|
||||
The packages are built and served by launchpad.net. We generate a Debian source package
|
||||
for each distribution and upload it. Their builder picks up the source package, builds it
|
||||
and installs the new version into the PPA repository. Launchpad requires a valid signature
|
||||
by a team member for source package uploads. The signing key is stored in an environment
|
||||
variable which Travis CI makes available to certain builds.
|
||||
|
||||
We want to build go-ethereum with the most recent version of Go, irrespective of the Go
|
||||
version that is available in the main Ubuntu repository. In order to make this possible,
|
||||
our PPA depends on the ~gophers/ubuntu/archive PPA. Our source package build-depends on
|
||||
golang-1.6, which is co-installable alongside the regular golang package. PPA dependencies
|
||||
can be edited at https://launchpad.net/%7Elp-fjl/+archive/ubuntu/geth-ci-testing/+edit-dependencies
|
||||
|
497
build/ci.go
Normal file
497
build/ci.go
Normal file
@ -0,0 +1,497 @@
|
||||
// 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 none
|
||||
|
||||
/*
|
||||
The ci command is called from Continuous Integration scripts.
|
||||
|
||||
Usage: go run ci.go <command> <command flags/arguments>
|
||||
|
||||
Available commands are:
|
||||
|
||||
install [ packages... ] -- builds packages and executables
|
||||
test [ -coverage ] [ -vet ] [ packages... ] -- runs the tests
|
||||
archive [ -type zip|tar ] -- archives build artefacts
|
||||
importkeys -- imports signing keys from env
|
||||
debsrc [ -sign key-id ] [ -upload dest ] -- creates a debian source package
|
||||
xgo [ options ] -- cross builds according to options
|
||||
|
||||
For all commands, -n prevents execution of external programs (dry run mode).
|
||||
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"../internal/build"
|
||||
)
|
||||
|
||||
var (
|
||||
// Files that end up in the geth*.zip archive.
|
||||
gethArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("geth"),
|
||||
}
|
||||
|
||||
// Files that end up in the geth-alltools*.zip archive.
|
||||
allToolsArchiveFiles = []string{
|
||||
"COPYING",
|
||||
executablePath("abigen"),
|
||||
executablePath("evm"),
|
||||
executablePath("geth"),
|
||||
executablePath("rlpdump"),
|
||||
}
|
||||
|
||||
// A debian package is created for all executables listed here.
|
||||
debExecutables = []debExecutable{
|
||||
{
|
||||
Name: "geth",
|
||||
Description: "Ethereum CLI client.",
|
||||
},
|
||||
{
|
||||
Name: "rlpdump",
|
||||
Description: "Developer utility tool that prints RLP structures.",
|
||||
},
|
||||
{
|
||||
Name: "evm",
|
||||
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
||||
},
|
||||
{
|
||||
Name: "abigen",
|
||||
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
||||
},
|
||||
}
|
||||
|
||||
// Distros for which packages are created.
|
||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||
debDistros = []string{"trusty", "wily", "xenial", "yakkety"}
|
||||
)
|
||||
|
||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||
|
||||
func executablePath(name string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
name += ".exe"
|
||||
}
|
||||
return filepath.Join(GOBIN, name)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) {
|
||||
log.Fatal("this script must be run from the root of the repository")
|
||||
}
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal("need subcommand as first argument")
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "install":
|
||||
doInstall(os.Args[2:])
|
||||
case "test":
|
||||
doTest(os.Args[2:])
|
||||
case "archive":
|
||||
doArchive(os.Args[2:])
|
||||
case "debsrc":
|
||||
doDebianSource(os.Args[2:])
|
||||
case "travis-debsrc":
|
||||
doTravisDebianSource(os.Args[2:])
|
||||
case "xgo":
|
||||
doXgo(os.Args[2:])
|
||||
default:
|
||||
log.Fatal("unknown command ", os.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Compiling
|
||||
|
||||
func doInstall(cmdline []string) {
|
||||
commitHash := flag.String("gitcommit", "", "Git commit hash embedded into binary.")
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
||||
// Check Go version. People regularly open issues about compilation
|
||||
// failure with outdated Go. This should save them the trouble.
|
||||
if runtime.Version() < "go1.4" && !strings.HasPrefix(runtime.Version(), "devel") {
|
||||
log.Println("You have Go version", runtime.Version())
|
||||
log.Println("go-ethereum requires at least Go version 1.4 and cannot")
|
||||
log.Println("be compiled with an earlier version. Please upgrade your Go installation.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Compile packages given as arguments, or everything if there are no arguments.
|
||||
packages := []string{"./..."}
|
||||
if flag.NArg() > 0 {
|
||||
packages = flag.Args()
|
||||
}
|
||||
|
||||
goinstall := goTool("install", makeBuildFlags(*commitHash)...)
|
||||
goinstall.Args = append(goinstall.Args, "-v")
|
||||
goinstall.Args = append(goinstall.Args, packages...)
|
||||
build.MustRun(goinstall)
|
||||
}
|
||||
|
||||
func makeBuildFlags(commitHash string) (flags []string) {
|
||||
// Since Go 1.5, the separator char for link time assignments
|
||||
// is '=' and using ' ' prints a warning. However, Go < 1.5 does
|
||||
// not support using '='.
|
||||
sep := " "
|
||||
if runtime.Version() > "go1.5" || strings.Contains(runtime.Version(), "devel") {
|
||||
sep = "="
|
||||
}
|
||||
|
||||
if os.Getenv("GO_OPENCL") != "" {
|
||||
flags = append(flags, "-tags", "opencl")
|
||||
}
|
||||
|
||||
// Set gitCommit constant via link-time assignment. If this is a git checkout, we can
|
||||
// just get the current commit hash through git. Otherwise we fall back to the hash
|
||||
// that was passed as -gitcommit.
|
||||
//
|
||||
// -gitcommit is required for Debian package builds. The source package doesn't
|
||||
// contain .git but we still want to embed the commit hash into the packaged binary.
|
||||
// The hash is rendered into the debian/rules build script when the source package is
|
||||
// created.
|
||||
if _, err := os.Stat(filepath.Join(".git", "HEAD")); !os.IsNotExist(err) {
|
||||
if c := build.GitCommit(); c != "" {
|
||||
commitHash = c
|
||||
}
|
||||
}
|
||||
if commitHash != "" {
|
||||
flags = append(flags, "-ldflags", "-X main.gitCommit"+sep+commitHash)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func goTool(subcmd string, args ...string) *exec.Cmd {
|
||||
gocmd := filepath.Join(runtime.GOROOT(), "bin", "go")
|
||||
cmd := exec.Command(gocmd, subcmd)
|
||||
cmd.Args = append(cmd.Args, args...)
|
||||
cmd.Env = []string{
|
||||
"GOPATH=" + build.GOPATH(),
|
||||
"GOBIN=" + GOBIN,
|
||||
}
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, e)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Running The Tests
|
||||
//
|
||||
// "tests" also includes static analysis tools such as vet.
|
||||
|
||||
func doTest(cmdline []string) {
|
||||
var (
|
||||
vet = flag.Bool("vet", false, "Whether to run go vet")
|
||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
packages := []string{"./..."}
|
||||
if len(flag.CommandLine.Args()) > 0 {
|
||||
packages = flag.CommandLine.Args()
|
||||
}
|
||||
|
||||
// Run analysis tools before the tests.
|
||||
if *vet {
|
||||
build.MustRun(goTool("vet", packages...))
|
||||
}
|
||||
|
||||
// Run the actual tests.
|
||||
gotest := goTool("test")
|
||||
if *coverage {
|
||||
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
||||
}
|
||||
gotest.Args = append(gotest.Args, packages...)
|
||||
build.MustRun(gotest)
|
||||
}
|
||||
|
||||
// Release Packaging
|
||||
|
||||
func doArchive(cmdline []string) {
|
||||
var (
|
||||
atype = flag.String("type", "zip", "Type of archive to write (zip|tar)")
|
||||
ext string
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
switch *atype {
|
||||
case "zip":
|
||||
ext = ".zip"
|
||||
case "tar":
|
||||
ext = ".tar.gz"
|
||||
default:
|
||||
log.Fatal("unknown archive type: ", atype)
|
||||
}
|
||||
base := makeArchiveBasename()
|
||||
if err := build.WriteArchive("geth-"+base, ext, gethArchiveFiles); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := build.WriteArchive("geth-alltools-"+base, ext, allToolsArchiveFiles); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func makeArchiveBasename() string {
|
||||
// date := time.Now().UTC().Format("200601021504")
|
||||
platform := runtime.GOOS + "-" + runtime.GOARCH
|
||||
archive := platform + "-" + build.VERSION()
|
||||
if commit := build.GitCommit(); commit != "" {
|
||||
archive += "-" + commit[:8]
|
||||
}
|
||||
return archive
|
||||
}
|
||||
|
||||
// Debian Packaging
|
||||
|
||||
// CLI entry point for Travis CI.
|
||||
func doTravisDebianSource(cmdline []string) {
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
||||
// Package only whitelisted branches.
|
||||
switch {
|
||||
case os.Getenv("TRAVIS_REPO_SLUG") != "ethereum/go-ethereum":
|
||||
log.Printf("skipping because this is a fork build")
|
||||
return
|
||||
case os.Getenv("TRAVIS_PULL_REQUEST") != "false":
|
||||
log.Printf("skipping because this is a PR build")
|
||||
return
|
||||
case os.Getenv("TRAVIS_BRANCH") != "develop" && !strings.HasPrefix(os.Getenv("TRAVIS_TAG"), "v1."):
|
||||
log.Printf("skipping because branch %q tag %q is not on the whitelist",
|
||||
os.Getenv("TRAVIS_BRANCH"),
|
||||
os.Getenv("TRAVIS_TAG"))
|
||||
return
|
||||
}
|
||||
|
||||
// Import the signing key.
|
||||
if b64key := os.Getenv("PPA_SIGNING_KEY"); b64key != "" {
|
||||
key, err := base64.StdEncoding.DecodeString(b64key)
|
||||
if err != nil {
|
||||
log.Fatal("invalid base64 PPA_SIGNING_KEY")
|
||||
}
|
||||
gpg := exec.Command("gpg", "--import")
|
||||
gpg.Stdin = bytes.NewReader(key)
|
||||
build.MustRun(gpg)
|
||||
}
|
||||
|
||||
// Assign unstable status to non-tag builds.
|
||||
unstable := "true"
|
||||
if os.Getenv("TRAVIS_BRANCH") != "develop" && os.Getenv("TRAVIS_TAG") != "" {
|
||||
unstable = "false"
|
||||
}
|
||||
|
||||
doDebianSource([]string{
|
||||
"-signer", "Felix Lange (Geth CI Testing Key) <fjl@twurst.com>",
|
||||
"-buildnum", os.Getenv("TRAVIS_BUILD_NUMBER"),
|
||||
"-upload", "ppa:lp-fjl/geth-ci-testing",
|
||||
"-unstable", unstable,
|
||||
})
|
||||
}
|
||||
|
||||
// CLI entry point for doing packaging locally.
|
||||
func doDebianSource(cmdline []string) {
|
||||
var (
|
||||
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
||||
upload = flag.String("upload", "", `Where to upload the source package (usually "ppa:ethereum/ethereum")`)
|
||||
buildnum = flag.String("buildnum", "", `Build number (included in version)`)
|
||||
unstable = flag.Bool("unstable", false, `Use package name suffix "-unstable"`)
|
||||
now = time.Now()
|
||||
)
|
||||
flag.CommandLine.Parse(cmdline)
|
||||
|
||||
// Create the debian worktree in /tmp.
|
||||
tmpdir, err := ioutil.TempDir("", "eth-deb-build-")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, distro := range debDistros {
|
||||
meta := newDebMetadata(distro, *signer, *buildnum, *unstable, now)
|
||||
pkgdir := stageDebianSource(tmpdir, meta)
|
||||
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
|
||||
debuild.Dir = pkgdir
|
||||
build.MustRun(debuild)
|
||||
|
||||
changes := fmt.Sprintf("%s_%s_source.changes", meta.Name(), meta.VersionString())
|
||||
changes = filepath.Join(tmpdir, changes)
|
||||
if *signer != "" {
|
||||
build.MustRunCommand("debsign", changes)
|
||||
}
|
||||
if *upload != "" {
|
||||
build.MustRunCommand("dput", *upload, changes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type debExecutable struct {
|
||||
Name, Description string
|
||||
}
|
||||
|
||||
type debMetadata struct {
|
||||
// go-ethereum version being built. Note that this
|
||||
// is not the debian package version. The package version
|
||||
// is constructed by VersionString.
|
||||
Version string
|
||||
|
||||
Author string // "name <email>", also selects signing key
|
||||
Buildnum string // build number
|
||||
Distro, Commit, Time string
|
||||
Executables []debExecutable
|
||||
Unstable bool
|
||||
}
|
||||
|
||||
func newDebMetadata(distro, author, buildnum string, unstable bool, t time.Time) debMetadata {
|
||||
if author == "" {
|
||||
// No signing key, use default author.
|
||||
author = "Ethereum Builds <fjl@ethereum.org>"
|
||||
}
|
||||
return debMetadata{
|
||||
Unstable: unstable,
|
||||
Author: author,
|
||||
Distro: distro,
|
||||
Commit: build.GitCommit(),
|
||||
Version: build.VERSION(),
|
||||
Buildnum: buildnum,
|
||||
Time: t.Format(time.RFC1123Z),
|
||||
Executables: debExecutables,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the metapackage that depends
|
||||
// on all executable packages.
|
||||
func (meta debMetadata) Name() string {
|
||||
if meta.Unstable {
|
||||
return "ethereum-unstable"
|
||||
}
|
||||
return "ethereum"
|
||||
}
|
||||
|
||||
// VersionString returns the debian version of the packages.
|
||||
func (meta debMetadata) VersionString() string {
|
||||
vsn := meta.Version
|
||||
if meta.Buildnum != "" {
|
||||
vsn += "+build" + meta.Buildnum
|
||||
}
|
||||
if meta.Distro != "" {
|
||||
vsn += "+" + meta.Distro
|
||||
}
|
||||
return vsn
|
||||
}
|
||||
|
||||
// ExeList returns the list of all executable packages.
|
||||
func (meta debMetadata) ExeList() string {
|
||||
names := make([]string, len(meta.Executables))
|
||||
for i, e := range meta.Executables {
|
||||
names[i] = meta.ExeName(e)
|
||||
}
|
||||
return strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
// ExeName returns the package name of an executable package.
|
||||
func (meta debMetadata) ExeName(exe debExecutable) string {
|
||||
if meta.Unstable {
|
||||
return exe.Name + "-unstable"
|
||||
}
|
||||
return exe.Name
|
||||
}
|
||||
|
||||
// ExeConflicts returns the content of the Conflicts field
|
||||
// for executable packages.
|
||||
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||
if meta.Unstable {
|
||||
// Set up the conflicts list so that the *-unstable packages
|
||||
// cannot be installed alongside the regular version.
|
||||
//
|
||||
// https://www.debian.org/doc/debian-policy/ch-relationships.html
|
||||
// is very explicit about Conflicts: and says that Breaks: should
|
||||
// be preferred and the conflicting files should be handled via
|
||||
// alternates. We might do this eventually but using a conflict is
|
||||
// easier now.
|
||||
return "ethereum, " + exe.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
|
||||
pkg := meta.Name() + "-" + meta.VersionString()
|
||||
pkgdir = filepath.Join(tmpdir, pkg)
|
||||
if err := os.Mkdir(pkgdir, 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Copy the source code.
|
||||
build.MustRunCommand("git", "checkout-index", "-a", "--prefix", pkgdir+string(filepath.Separator))
|
||||
|
||||
// Put the debian build files in place.
|
||||
debian := filepath.Join(pkgdir, "debian")
|
||||
build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
||||
build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
||||
build.Render("build/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
||||
build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
||||
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
||||
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
||||
for _, exe := range meta.Executables {
|
||||
install := filepath.Join(debian, exe.Name+".install")
|
||||
docs := filepath.Join(debian, exe.Name+".docs")
|
||||
build.Render("build/deb.install", install, 0644, exe)
|
||||
build.Render("build/deb.docs", docs, 0644, exe)
|
||||
}
|
||||
|
||||
return pkgdir
|
||||
}
|
||||
|
||||
// Cross compilation
|
||||
|
||||
func doXgo(cmdline []string) {
|
||||
// Make sure xgo is available for cross compilation
|
||||
gogetxgo := goTool("get", "github.com/karalabe/xgo")
|
||||
build.MustRun(gogetxgo)
|
||||
|
||||
// Execute the actual cross compilation
|
||||
pkg := cmdline[len(cmdline)-1]
|
||||
args := append(cmdline[:len(cmdline)-1], makeBuildFlags("")...)
|
||||
|
||||
build.MustRun(xgoTool(append(args, pkg)...))
|
||||
}
|
||||
|
||||
func xgoTool(args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(filepath.Join(GOBIN, "xgo"), args...)
|
||||
cmd.Env = []string{
|
||||
"GOPATH=" + build.GOPATH(),
|
||||
"GOBIN=" + GOBIN,
|
||||
}
|
||||
for _, e := range os.Environ() {
|
||||
if strings.HasPrefix(e, "GOPATH=") || strings.HasPrefix(e, "GOBIN=") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, e)
|
||||
}
|
||||
return cmd
|
||||
}
|
5
build/deb.changelog
Normal file
5
build/deb.changelog
Normal file
@ -0,0 +1,5 @@
|
||||
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||
|
||||
* git build of {{.Commit}}
|
||||
|
||||
-- {{.Author}} {{.Time}}
|
25
build/deb.control
Normal file
25
build/deb.control
Normal file
@ -0,0 +1,25 @@
|
||||
Source: {{.Name}}
|
||||
Section: science
|
||||
Priority: extra
|
||||
Maintainer: {{.Author}}
|
||||
Build-Depends: debhelper (>= 8.0.0), golang-1.6
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: https://ethereum.org
|
||||
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||
Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||
|
||||
Package: {{.Name}}
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, {{.ExeList}}
|
||||
Description: Meta-package to install geth and other tools
|
||||
Meta-package to install geth and other tools
|
||||
|
||||
{{range .Executables}}
|
||||
Package: {{$.ExeName .}}
|
||||
Conflicts: {{$.ExeConflicts .}}
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Built-Using: ${misc:Built-Using}
|
||||
Description: {{.Description}}
|
||||
{{.Description}}
|
||||
{{end}}
|
14
build/deb.copyright
Normal file
14
build/deb.copyright
Normal file
@ -0,0 +1,14 @@
|
||||
Copyright 2016 The go-ethereum Authors
|
||||
|
||||
go-ethereum is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
go-ethereum is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
1
build/deb.docs
Normal file
1
build/deb.docs
Normal file
@ -0,0 +1 @@
|
||||
AUTHORS
|
1
build/deb.install
Normal file
1
build/deb.install
Normal file
@ -0,0 +1 @@
|
||||
build/bin/{{.Name}} usr/bin
|
13
build/deb.rules
Normal file
13
build/deb.rules
Normal file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
override_dh_auto_build:
|
||||
build/env.sh /usr/lib/go-1.6/bin/go run build/ci.go install -gitcommit {{.Commit}}
|
||||
|
||||
override_dh_auto_test:
|
||||
|
||||
%:
|
||||
dh $@
|
@ -20,9 +20,8 @@ fi
|
||||
|
||||
# Set up the environment to use the workspace.
|
||||
# Also add Godeps workspace so we build using canned dependencies.
|
||||
GOPATH="$ethdir/go-ethereum/Godeps/_workspace:$workspace"
|
||||
GOBIN="$PWD/build/bin"
|
||||
export GOPATH GOBIN
|
||||
GOPATH="$workspace"
|
||||
export GOPATH
|
||||
|
||||
# Run the command inside the workspace.
|
||||
cd "$ethdir/go-ethereum"
|
||||
|
@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -f "build/env.sh" ]; then
|
||||
echo "$0 must be run from the root of the repository."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Since Go 1.5, the separator char for link time assignments
|
||||
# is '=' and using ' ' prints a warning. However, Go < 1.5 does
|
||||
# not support using '='.
|
||||
sep=$(go version | awk '{ if ($3 >= "go1.5" || index($3, "devel")) print "="; else print " "; }' -)
|
||||
|
||||
# set gitCommit when running from a Git checkout.
|
||||
if [ -f ".git/HEAD" ]; then
|
||||
echo "-ldflags '-X main.gitCommit$sep$(git rev-parse HEAD)'"
|
||||
fi
|
||||
|
||||
if [ ! -z "$GO_OPENCL" ]; then
|
||||
echo "-tags opencl"
|
||||
fi
|
@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(find ./* -maxdepth 10 -type d -not -path "./build" -not -path "./Godeps/*" ); do
|
||||
if ls $d/*.go &> /dev/null; then
|
||||
go test -coverprofile=profile.out -covermode=atomic $d
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
echo '<<<<<< EOF' >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
fi
|
||||
done
|
32
circle.yml
Normal file
32
circle.yml
Normal file
@ -0,0 +1,32 @@
|
||||
machine:
|
||||
services:
|
||||
- docker
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/.ethash" # Cache the ethash DAG generated by hive for consecutive builds
|
||||
- "~/.docker" # Cache all docker images manually to avoid lengthy rebuilds
|
||||
override:
|
||||
# Restore all previously cached docker images
|
||||
- mkdir -p ~/.docker
|
||||
- for img in `ls ~/.docker`; do docker load -i ~/.docker/$img; done
|
||||
|
||||
# Pull in and hive, restore cached ethash DAGs and do a dry run
|
||||
- go get -u github.com/karalabe/hive
|
||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && mkdir -p workspace/ethash/ ~/.ethash)
|
||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && cp -r ~/.ethash/. workspace/ethash/)
|
||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=NONE --test=. --sim=. --loglevel=6)
|
||||
|
||||
# Cache all the docker images and the ethash DAGs
|
||||
- for img in `docker images | grep -v "^<none>" | tail -n +2 | awk '{print $1}'`; do docker save $img > ~/.docker/`echo $img | tr '/' ':'`.tar; done
|
||||
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/ethash/. ~/.ethash
|
||||
|
||||
test:
|
||||
override:
|
||||
# Build Geth and move into a known folder
|
||||
- make geth
|
||||
- cp ./build/bin/geth $HOME/geth
|
||||
|
||||
# Run hive and move all generated logs into the public artifacts folder
|
||||
- (cd ~/.go_workspace/src/github.com/karalabe/hive && hive --docker-noshell --client=go-ethereum:local --override=$HOME/geth --test=. --sim=.)
|
||||
- cp -r ~/.go_workspace/src/github.com/karalabe/hive/workspace/logs/* $CIRCLE_ARTIFACTS
|
@ -19,15 +19,12 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
)
|
||||
@ -43,50 +40,43 @@ func main() {
|
||||
nodeKey *ecdsa.PrivateKey
|
||||
err error
|
||||
)
|
||||
flag.Var(glog.GetVerbosity(), "verbosity", "log verbosity (0-9)")
|
||||
flag.Var(glog.GetVModule(), "vmodule", "log verbosity pattern")
|
||||
glog.SetToStderr(true)
|
||||
flag.Parse()
|
||||
logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.DebugLevel))
|
||||
|
||||
if *genKey != "" {
|
||||
writeKey(*genKey)
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
utils.Fatalf("could not generate key: %v", err)
|
||||
}
|
||||
if err := crypto.SaveECDSA(*genKey, key); err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
natm, err := nat.Parse(*natdesc)
|
||||
if err != nil {
|
||||
log.Fatalf("-nat: %v", err)
|
||||
utils.Fatalf("-nat: %v", err)
|
||||
}
|
||||
switch {
|
||||
case *nodeKeyFile == "" && *nodeKeyHex == "":
|
||||
log.Fatal("Use -nodekey or -nodekeyhex to specify a private key")
|
||||
utils.Fatalf("Use -nodekey or -nodekeyhex to specify a private key")
|
||||
case *nodeKeyFile != "" && *nodeKeyHex != "":
|
||||
log.Fatal("Options -nodekey and -nodekeyhex are mutually exclusive")
|
||||
utils.Fatalf("Options -nodekey and -nodekeyhex are mutually exclusive")
|
||||
case *nodeKeyFile != "":
|
||||
if nodeKey, err = crypto.LoadECDSA(*nodeKeyFile); err != nil {
|
||||
log.Fatalf("-nodekey: %v", err)
|
||||
utils.Fatalf("-nodekey: %v", err)
|
||||
}
|
||||
case *nodeKeyHex != "":
|
||||
if nodeKey, err = crypto.HexToECDSA(*nodeKeyHex); err != nil {
|
||||
log.Fatalf("-nodekeyhex: %v", err)
|
||||
utils.Fatalf("-nodekeyhex: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := discover.ListenUDP(nodeKey, *listenAddr, natm, ""); err != nil {
|
||||
log.Fatal(err)
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
select {}
|
||||
}
|
||||
|
||||
func writeKey(target string) {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
log.Fatalf("could not generate key: %v", err)
|
||||
}
|
||||
b := crypto.FromECDSA(key)
|
||||
if target == "-" {
|
||||
fmt.Println(hex.EncodeToString(b))
|
||||
} else {
|
||||
if err := ioutil.WriteFile(target, b, 0600); err != nil {
|
||||
log.Fatal("write error: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -74,9 +74,9 @@ func runTestWithReader(test string, r io.Reader) error {
|
||||
var err error
|
||||
switch strings.ToLower(test) {
|
||||
case "bk", "block", "blocktest", "blockchaintest", "blocktests", "blockchaintests":
|
||||
err = tests.RunBlockTestWithReader(params.MainNetHomesteadBlock, r, skipTests)
|
||||
err = tests.RunBlockTestWithReader(params.MainNetHomesteadBlock, params.MainNetDAOForkBlock, r, skipTests)
|
||||
case "st", "state", "statetest", "statetests":
|
||||
rs := tests.RuleSet{HomesteadBlock: params.MainNetHomesteadBlock}
|
||||
rs := tests.RuleSet{HomesteadBlock: params.MainNetHomesteadBlock, DAOForkBlock: params.MainNetDAOForkBlock, DAOForkSupport: true}
|
||||
err = tests.RunStateTestWithReader(rs, r, skipTests)
|
||||
case "tx", "transactiontest", "transactiontests":
|
||||
err = tests.RunTransactionTestsWithReader(r, skipTests)
|
||||
@ -183,7 +183,7 @@ func runSuite(test, file string) {
|
||||
}
|
||||
}
|
||||
|
||||
func setupApp(c *cli.Context) {
|
||||
func setupApp(c *cli.Context) error {
|
||||
flagTest := c.GlobalString(TestFlag.Name)
|
||||
flagFile := c.GlobalString(FileFlag.Name)
|
||||
continueOnError = c.GlobalBool(ContinueOnErrorFlag.Name)
|
||||
@ -196,8 +196,8 @@ func setupApp(c *cli.Context) {
|
||||
if err := runTestWithReader(flagTest, os.Stdin); err != nil {
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
@ -33,6 +32,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -84,11 +84,16 @@ var (
|
||||
Name: "verbosity",
|
||||
Usage: "sets the verbosity level",
|
||||
}
|
||||
CreateFlag = cli.BoolFlag{
|
||||
Name: "create",
|
||||
Usage: "indicates the action should be create rather than call",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
app = utils.NewApp("0.2", "the evm command line interface")
|
||||
app.Flags = []cli.Flag{
|
||||
CreateFlag,
|
||||
DebugFlag,
|
||||
VerbosityFlag,
|
||||
ForceJitFlag,
|
||||
@ -104,15 +109,13 @@ func init() {
|
||||
app.Action = run
|
||||
}
|
||||
|
||||
func run(ctx *cli.Context) {
|
||||
func run(ctx *cli.Context) error {
|
||||
glog.SetToStderr(true)
|
||||
glog.SetV(ctx.GlobalInt(VerbosityFlag.Name))
|
||||
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, db)
|
||||
sender := statedb.CreateAccount(common.StringToAddress("sender"))
|
||||
receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
|
||||
receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)))
|
||||
|
||||
vmenv := NewEnv(statedb, common.StringToAddress("evmuser"), common.Big(ctx.GlobalString(ValueFlag.Name)), vm.Config{
|
||||
Debug: ctx.GlobalBool(DebugFlag.Name),
|
||||
@ -121,17 +124,37 @@ func run(ctx *cli.Context) {
|
||||
})
|
||||
|
||||
tstart := time.Now()
|
||||
ret, e := vmenv.Call(
|
||||
sender,
|
||||
receiver.Address(),
|
||||
common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
|
||||
common.Big(ctx.GlobalString(GasFlag.Name)),
|
||||
common.Big(ctx.GlobalString(PriceFlag.Name)),
|
||||
common.Big(ctx.GlobalString(ValueFlag.Name)),
|
||||
|
||||
var (
|
||||
ret []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if ctx.GlobalBool(CreateFlag.Name) {
|
||||
input := append(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)), common.Hex2Bytes(ctx.GlobalString(InputFlag.Name))...)
|
||||
ret, _, err = vmenv.Create(
|
||||
sender,
|
||||
input,
|
||||
common.Big(ctx.GlobalString(GasFlag.Name)),
|
||||
common.Big(ctx.GlobalString(PriceFlag.Name)),
|
||||
common.Big(ctx.GlobalString(ValueFlag.Name)),
|
||||
)
|
||||
} else {
|
||||
receiver := statedb.CreateAccount(common.StringToAddress("receiver"))
|
||||
receiver.SetCode(common.Hex2Bytes(ctx.GlobalString(CodeFlag.Name)))
|
||||
ret, err = vmenv.Call(
|
||||
sender,
|
||||
receiver.Address(),
|
||||
common.Hex2Bytes(ctx.GlobalString(InputFlag.Name)),
|
||||
common.Big(ctx.GlobalString(GasFlag.Name)),
|
||||
common.Big(ctx.GlobalString(PriceFlag.Name)),
|
||||
common.Big(ctx.GlobalString(ValueFlag.Name)),
|
||||
)
|
||||
}
|
||||
vmdone := time.Since(tstart)
|
||||
|
||||
if ctx.GlobalBool(DumpFlag.Name) {
|
||||
statedb.Commit()
|
||||
fmt.Println(string(statedb.Dump()))
|
||||
}
|
||||
vm.StdErrFormat(vmenv.StructLogs())
|
||||
@ -150,10 +173,11 @@ num gc: %d
|
||||
}
|
||||
|
||||
fmt.Printf("OUT: 0x%x", ret)
|
||||
if e != nil {
|
||||
fmt.Printf(" error: %v", e)
|
||||
if err != nil {
|
||||
fmt.Printf(" error: %v", err)
|
||||
}
|
||||
fmt.Println()
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -20,12 +20,13 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -69,7 +70,7 @@ either new or import). Without it you are not able to unlock your account.
|
||||
|
||||
Note that exporting your key in unencrypted format is NOT supported.
|
||||
|
||||
Keys are stored under <DATADIR>/keys.
|
||||
Keys are stored under <DATADIR>/keystore.
|
||||
It is safe to transfer the entire directory or the individual keys therein
|
||||
between ethereum nodes by simply copying.
|
||||
Make sure you backup your keys regularly.
|
||||
@ -166,11 +167,12 @@ nodes.
|
||||
}
|
||||
)
|
||||
|
||||
func accountList(ctx *cli.Context) {
|
||||
func accountList(ctx *cli.Context) error {
|
||||
accman := utils.MakeAccountManager(ctx)
|
||||
for i, acct := range accman.Accounts() {
|
||||
fmt.Printf("Account #%d: {%x} %s\n", i, acct.Address, acct.File)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// tries unlocking the specified account a few times.
|
||||
@ -215,12 +217,12 @@ func getPassPhrase(prompt string, confirmation bool, i int, passwords []string)
|
||||
if prompt != "" {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
password, err := utils.Stdin.PasswordPrompt("Passphrase: ")
|
||||
password, err := console.Stdin.PromptPassword("Passphrase: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||
}
|
||||
if confirmation {
|
||||
confirm, err := utils.Stdin.PasswordPrompt("Repeat passphrase: ")
|
||||
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
||||
}
|
||||
@ -258,7 +260,7 @@ func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrErro
|
||||
}
|
||||
|
||||
// accountCreate creates a new account into the keystore defined by the CLI flags.
|
||||
func accountCreate(ctx *cli.Context) {
|
||||
func accountCreate(ctx *cli.Context) error {
|
||||
accman := utils.MakeAccountManager(ctx)
|
||||
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
|
||||
|
||||
@ -267,11 +269,12 @@ func accountCreate(ctx *cli.Context) {
|
||||
utils.Fatalf("Failed to create account: %v", err)
|
||||
}
|
||||
fmt.Printf("Address: {%x}\n", account.Address)
|
||||
return nil
|
||||
}
|
||||
|
||||
// accountUpdate transitions an account from a previous format to the current
|
||||
// one, also providing the possibility to change the pass-phrase.
|
||||
func accountUpdate(ctx *cli.Context) {
|
||||
func accountUpdate(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) == 0 {
|
||||
utils.Fatalf("No accounts specified to update")
|
||||
}
|
||||
@ -282,9 +285,10 @@ func accountUpdate(ctx *cli.Context) {
|
||||
if err := accman.Update(account, oldPassword, newPassword); err != nil {
|
||||
utils.Fatalf("Could not update the account: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importWallet(ctx *cli.Context) {
|
||||
func importWallet(ctx *cli.Context) error {
|
||||
keyfile := ctx.Args().First()
|
||||
if len(keyfile) == 0 {
|
||||
utils.Fatalf("keyfile must be given as argument")
|
||||
@ -302,9 +306,10 @@ func importWallet(ctx *cli.Context) {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
fmt.Printf("Address: {%x}\n", acct.Address)
|
||||
return nil
|
||||
}
|
||||
|
||||
func accountImport(ctx *cli.Context) {
|
||||
func accountImport(ctx *cli.Context) error {
|
||||
keyfile := ctx.Args().First()
|
||||
if len(keyfile) == 0 {
|
||||
utils.Fatalf("keyfile must be given as argument")
|
||||
@ -320,4 +325,5 @@ func accountImport(ctx *cli.Context) {
|
||||
utils.Fatalf("Could not create the account: %v", err)
|
||||
}
|
||||
fmt.Printf("Address: {%x}\n", acct.Address)
|
||||
return nil
|
||||
}
|
||||
|
@ -23,14 +23,15 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -71,7 +72,7 @@ Use "ethereum dump 0" to dump the genesis block.
|
||||
}
|
||||
)
|
||||
|
||||
func importChain(ctx *cli.Context) {
|
||||
func importChain(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) != 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
@ -83,9 +84,10 @@ func importChain(ctx *cli.Context) {
|
||||
utils.Fatalf("Import error: %v", err)
|
||||
}
|
||||
fmt.Printf("Import done in %v", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportChain(ctx *cli.Context) {
|
||||
func exportChain(ctx *cli.Context) error {
|
||||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
@ -113,10 +115,11 @@ func exportChain(ctx *cli.Context) {
|
||||
utils.Fatalf("Export error: %v\n", err)
|
||||
}
|
||||
fmt.Printf("Export done in %v", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeDB(ctx *cli.Context) {
|
||||
confirm, err := utils.Stdin.ConfirmPrompt("Remove local database?")
|
||||
func removeDB(ctx *cli.Context) error {
|
||||
confirm, err := console.Stdin.PromptConfirm("Remove local database?")
|
||||
if err != nil {
|
||||
utils.Fatalf("%v", err)
|
||||
}
|
||||
@ -131,9 +134,10 @@ func removeDB(ctx *cli.Context) {
|
||||
} else {
|
||||
fmt.Println("Operation aborted")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func upgradeDB(ctx *cli.Context) {
|
||||
func upgradeDB(ctx *cli.Context) error {
|
||||
glog.Infoln("Upgrading blockchain database")
|
||||
|
||||
chain, chainDb := utils.MakeChain(ctx)
|
||||
@ -162,9 +166,10 @@ func upgradeDB(ctx *cli.Context) {
|
||||
os.Remove(exportFile)
|
||||
glog.Infoln("Import finished")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dump(ctx *cli.Context) {
|
||||
func dump(ctx *cli.Context) error {
|
||||
chain, chainDb := utils.MakeChain(ctx)
|
||||
for _, arg := range ctx.Args() {
|
||||
var block *types.Block
|
||||
@ -181,12 +186,12 @@ func dump(ctx *cli.Context) {
|
||||
state, err := state.New(block.Root(), chainDb)
|
||||
if err != nil {
|
||||
utils.Fatalf("could not create new state: %v", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", state.Dump())
|
||||
}
|
||||
}
|
||||
chainDb.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashish returns true for strings that look like hashes.
|
||||
|
173
cmd/geth/consolecmd.go
Normal file
173
cmd/geth/consolecmd.go
Normal file
@ -0,0 +1,173 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
consoleCommand = cli.Command{
|
||||
Action: localConsole,
|
||||
Name: "console",
|
||||
Usage: `Geth Console: interactive JavaScript environment`,
|
||||
Description: `
|
||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
||||
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
||||
`,
|
||||
}
|
||||
attachCommand = cli.Command{
|
||||
Action: remoteConsole,
|
||||
Name: "attach",
|
||||
Usage: `Geth Console: interactive JavaScript environment (connect to node)`,
|
||||
Description: `
|
||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
||||
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
|
||||
This command allows to open a console on a running geth node.
|
||||
`,
|
||||
}
|
||||
javascriptCommand = cli.Command{
|
||||
Action: ephemeralConsole,
|
||||
Name: "js",
|
||||
Usage: `executes the given JavaScript files in the Geth JavaScript VM`,
|
||||
Description: `
|
||||
The JavaScript VM exposes a node admin interface as well as the Ðapp
|
||||
JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
||||
`,
|
||||
}
|
||||
)
|
||||
|
||||
// localConsole starts a new geth node, attaching a JavaScript console to it at the
|
||||
// same time.
|
||||
func localConsole(ctx *cli.Context) error {
|
||||
// Create and start the node based on the CLI flags
|
||||
node := utils.MakeSystemNode(clientIdentifier, verString, relConfig, makeDefaultExtra(), ctx)
|
||||
startNode(ctx, node)
|
||||
defer node.Stop()
|
||||
|
||||
// Attach to the newly started node and start the JavaScript console
|
||||
client, err := node.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||
}
|
||||
config := console.Config{
|
||||
DataDir: node.DataDir(),
|
||||
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
||||
Client: client,
|
||||
Preload: utils.MakeConsolePreloads(ctx),
|
||||
}
|
||||
console, err := console.New(config)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
||||
}
|
||||
defer console.Stop(false)
|
||||
|
||||
// If only a short execution was requested, evaluate and return
|
||||
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
|
||||
console.Evaluate(script)
|
||||
return nil
|
||||
}
|
||||
// Otherwise print the welcome screen and enter interactive mode
|
||||
console.Welcome()
|
||||
console.Interactive()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// remoteConsole will connect to a remote geth instance, attaching a JavaScript
|
||||
// console to it.
|
||||
func remoteConsole(ctx *cli.Context) error {
|
||||
// Attach to a remotely running geth instance and start the JavaScript console
|
||||
client, err := utils.NewRemoteRPCClient(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("Unable to attach to remote geth: %v", err)
|
||||
}
|
||||
config := console.Config{
|
||||
DataDir: utils.MustMakeDataDir(ctx),
|
||||
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
||||
Client: client,
|
||||
Preload: utils.MakeConsolePreloads(ctx),
|
||||
}
|
||||
console, err := console.New(config)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
||||
}
|
||||
defer console.Stop(false)
|
||||
|
||||
// If only a short execution was requested, evaluate and return
|
||||
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
|
||||
console.Evaluate(script)
|
||||
return nil
|
||||
}
|
||||
// Otherwise print the welcome screen and enter interactive mode
|
||||
console.Welcome()
|
||||
console.Interactive()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ephemeralConsole starts a new geth node, attaches an ephemeral JavaScript
|
||||
// console to it, and each of the files specified as arguments and tears the
|
||||
// everything down.
|
||||
func ephemeralConsole(ctx *cli.Context) error {
|
||||
// Create and start the node based on the CLI flags
|
||||
node := utils.MakeSystemNode(clientIdentifier, verString, relConfig, makeDefaultExtra(), ctx)
|
||||
startNode(ctx, node)
|
||||
defer node.Stop()
|
||||
|
||||
// Attach to the newly started node and start the JavaScript console
|
||||
client, err := node.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||
}
|
||||
config := console.Config{
|
||||
DataDir: node.DataDir(),
|
||||
DocRoot: ctx.GlobalString(utils.JSpathFlag.Name),
|
||||
Client: client,
|
||||
Preload: utils.MakeConsolePreloads(ctx),
|
||||
}
|
||||
console, err := console.New(config)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to start the JavaScript console: %v", err)
|
||||
}
|
||||
defer console.Stop(false)
|
||||
|
||||
// Evaluate each of the specified JavaScript files
|
||||
for _, file := range ctx.Args() {
|
||||
if err = console.Execute(file); err != nil {
|
||||
utils.Fatalf("Failed to execute %s: %v", file, err)
|
||||
}
|
||||
}
|
||||
// Wait for pending callbacks, but stop for Ctrl-C.
|
||||
abort := make(chan os.Signal, 1)
|
||||
signal.Notify(abort, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
<-abort
|
||||
os.Exit(0)
|
||||
}()
|
||||
console.Stop(true)
|
||||
|
||||
return nil
|
||||
}
|
170
cmd/geth/consolecmd_test.go
Normal file
170
cmd/geth/consolecmd_test.go
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
// Tests that a node embedded within a console can be started up properly and
|
||||
// then terminated by closing the input stream.
|
||||
func TestConsoleWelcome(t *testing.T) {
|
||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||
|
||||
// Start a geth console, make sure it's cleaned up and terminate the console
|
||||
geth := runGeth(t,
|
||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||
"--etherbase", coinbase, "--shh",
|
||||
"console")
|
||||
|
||||
// Gather all the infos the welcome message needs to contain
|
||||
geth.setTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||
geth.setTemplateFunc("gover", runtime.Version)
|
||||
geth.setTemplateFunc("gethver", func() string { return verString })
|
||||
geth.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||
geth.setTemplateFunc("apis", func() []string {
|
||||
apis := append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi)
|
||||
sort.Strings(apis)
|
||||
return apis
|
||||
})
|
||||
|
||||
// Verify the actual welcome message to the required template
|
||||
geth.expect(`
|
||||
Welcome to the Geth JavaScript console!
|
||||
|
||||
instance: Geth/v{{gethver}}/{{goos}}/{{gover}}
|
||||
coinbase: {{.Etherbase}}
|
||||
at block: 0 ({{niltime}})
|
||||
datadir: {{.Datadir}}
|
||||
modules:{{range apis}} {{.}}:1.0{{end}}
|
||||
|
||||
> {{.InputLine "exit"}}
|
||||
`)
|
||||
geth.expectExit()
|
||||
}
|
||||
|
||||
// Tests that a console can be attached to a running node via various means.
|
||||
func TestIPCAttachWelcome(t *testing.T) {
|
||||
// Configure the instance for IPC attachement
|
||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||
var ipc string
|
||||
if runtime.GOOS == "windows" {
|
||||
ipc = `\\.\pipe\geth` + strconv.Itoa(trulyRandInt(100000, 999999))
|
||||
} else {
|
||||
ws := tmpdir(t)
|
||||
defer os.RemoveAll(ws)
|
||||
ipc = filepath.Join(ws, "geth.ipc")
|
||||
}
|
||||
// Note: we need --shh because testAttachWelcome checks for default
|
||||
// list of ipc modules and shh is included there.
|
||||
geth := runGeth(t,
|
||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
|
||||
|
||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
||||
testAttachWelcome(t, geth, "ipc:"+ipc)
|
||||
|
||||
geth.interrupt()
|
||||
geth.expectExit()
|
||||
}
|
||||
|
||||
func TestHTTPAttachWelcome(t *testing.T) {
|
||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
||||
geth := runGeth(t,
|
||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||
"--etherbase", coinbase, "--rpc", "--rpcport", port)
|
||||
|
||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
||||
testAttachWelcome(t, geth, "http://localhost:"+port)
|
||||
|
||||
geth.interrupt()
|
||||
geth.expectExit()
|
||||
}
|
||||
|
||||
func TestWSAttachWelcome(t *testing.T) {
|
||||
coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||
port := strconv.Itoa(trulyRandInt(1024, 65536)) // Yeah, sometimes this will fail, sorry :P
|
||||
|
||||
geth := runGeth(t,
|
||||
"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none",
|
||||
"--etherbase", coinbase, "--ws", "--wsport", port)
|
||||
|
||||
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
|
||||
testAttachWelcome(t, geth, "ws://localhost:"+port)
|
||||
|
||||
geth.interrupt()
|
||||
geth.expectExit()
|
||||
}
|
||||
|
||||
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint string) {
|
||||
// Attach to a running geth note and terminate immediately
|
||||
attach := runGeth(t, "attach", endpoint)
|
||||
defer attach.expectExit()
|
||||
attach.stdin.Close()
|
||||
|
||||
// Gather all the infos the welcome message needs to contain
|
||||
attach.setTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||
attach.setTemplateFunc("gover", runtime.Version)
|
||||
attach.setTemplateFunc("gethver", func() string { return verString })
|
||||
attach.setTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
||||
attach.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||
attach.setTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||
attach.setTemplateFunc("datadir", func() string { return geth.Datadir })
|
||||
attach.setTemplateFunc("apis", func() []string {
|
||||
var apis []string
|
||||
if strings.HasPrefix(endpoint, "ipc") {
|
||||
apis = append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi)
|
||||
} else {
|
||||
apis = append(strings.Split(rpc.DefaultHTTPApis, ","), rpc.MetadataApi)
|
||||
}
|
||||
sort.Strings(apis)
|
||||
return apis
|
||||
})
|
||||
|
||||
// Verify the actual welcome message to the required template
|
||||
attach.expect(`
|
||||
Welcome to the Geth JavaScript console!
|
||||
|
||||
instance: Geth/v{{gethver}}/{{goos}}/{{gover}}
|
||||
coinbase: {{etherbase}}
|
||||
at block: 0 ({{niltime}}){{if ipc}}
|
||||
datadir: {{datadir}}{{end}}
|
||||
modules:{{range apis}} {{.}}:1.0{{end}}
|
||||
|
||||
> {{.InputLine "exit" }}
|
||||
`)
|
||||
attach.expectExit()
|
||||
}
|
||||
|
||||
// trulyRandInt generates a crypto random integer used by the console tests to
|
||||
// not clash network ports with other tests running cocurrently.
|
||||
func trulyRandInt(lo, hi int) int {
|
||||
num, _ := rand.Int(rand.Reader, big.NewInt(int64(hi-lo)))
|
||||
return int(num.Int64()) + lo
|
||||
}
|
232
cmd/geth/dao_test.go
Normal file
232
cmd/geth/dao_test.go
Normal file
@ -0,0 +1,232 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// Genesis block for nodes which don't care about the DAO fork (i.e. not configured)
|
||||
var daoOldGenesis = `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {}
|
||||
}`
|
||||
|
||||
// Genesis block for nodes which actively oppose the DAO fork
|
||||
var daoNoForkGenesis = `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"daoForkBlock" : 314,
|
||||
"daoForkSupport" : false
|
||||
}
|
||||
}`
|
||||
|
||||
// Genesis block for nodes which actively support the DAO fork
|
||||
var daoProForkGenesis = `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"daoForkBlock" : 314,
|
||||
"daoForkSupport" : true
|
||||
}
|
||||
}`
|
||||
|
||||
var daoGenesisHash = common.HexToHash("5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0")
|
||||
var daoGenesisForkBlock = big.NewInt(314)
|
||||
|
||||
// Tests that the DAO hard-fork number and the nodes support/opposition is correctly
|
||||
// set in the database after various initialization procedures and invocations.
|
||||
func TestDAODefaultMainnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, "", [][2]bool{{false, false}}, params.MainNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOSupportMainnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, "", [][2]bool{{true, false}}, params.MainNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOOpposeMainnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, "", [][2]bool{{false, true}}, params.MainNetDAOForkBlock, false)
|
||||
}
|
||||
func TestDAOSwitchToSupportMainnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, "", [][2]bool{{false, true}, {true, false}}, params.MainNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOSwitchToOpposeMainnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, "", [][2]bool{{true, false}, {false, true}}, params.MainNetDAOForkBlock, false)
|
||||
}
|
||||
func TestDAODefaultTestnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, true, "", [][2]bool{{false, false}}, params.TestNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOSupportTestnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, true, "", [][2]bool{{true, false}}, params.TestNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOOpposeTestnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, true, "", [][2]bool{{false, true}}, params.TestNetDAOForkBlock, false)
|
||||
}
|
||||
func TestDAOSwitchToSupportTestnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, true, "", [][2]bool{{false, true}, {true, false}}, params.TestNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOSwitchToOpposeTestnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, true, "", [][2]bool{{true, false}, {false, true}}, params.TestNetDAOForkBlock, false)
|
||||
}
|
||||
func TestDAOInitOldPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{}, nil, false)
|
||||
}
|
||||
func TestDAODefaultOldPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{false, false}}, params.MainNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOSupportOldPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{true, false}}, params.MainNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOOpposeOldPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{false, true}}, params.MainNetDAOForkBlock, false)
|
||||
}
|
||||
func TestDAOSwitchToSupportOldPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{false, true}, {true, false}}, params.MainNetDAOForkBlock, true)
|
||||
}
|
||||
func TestDAOSwitchToOpposeOldPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoOldGenesis, [][2]bool{{true, false}, {false, true}}, params.MainNetDAOForkBlock, false)
|
||||
}
|
||||
func TestDAOInitNoForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{}, daoGenesisForkBlock, false)
|
||||
}
|
||||
func TestDAODefaultNoForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{false, false}}, daoGenesisForkBlock, false)
|
||||
}
|
||||
func TestDAOSupportNoForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{true, false}}, daoGenesisForkBlock, true)
|
||||
}
|
||||
func TestDAOOpposeNoForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{false, true}}, daoGenesisForkBlock, false)
|
||||
}
|
||||
func TestDAOSwitchToSupportNoForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{false, true}, {true, false}}, daoGenesisForkBlock, true)
|
||||
}
|
||||
func TestDAOSwitchToOpposeNoForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoNoForkGenesis, [][2]bool{{true, false}, {false, true}}, daoGenesisForkBlock, false)
|
||||
}
|
||||
func TestDAOInitProForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{}, daoGenesisForkBlock, true)
|
||||
}
|
||||
func TestDAODefaultProForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{false, false}}, daoGenesisForkBlock, true)
|
||||
}
|
||||
func TestDAOSupportProForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{true, false}}, daoGenesisForkBlock, true)
|
||||
}
|
||||
func TestDAOOpposeProForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{false, true}}, daoGenesisForkBlock, false)
|
||||
}
|
||||
func TestDAOSwitchToSupportProForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{false, true}, {true, false}}, daoGenesisForkBlock, true)
|
||||
}
|
||||
func TestDAOSwitchToOpposeProForkPrivnet(t *testing.T) {
|
||||
testDAOForkBlockNewChain(t, false, daoProForkGenesis, [][2]bool{{true, false}, {false, true}}, daoGenesisForkBlock, false)
|
||||
}
|
||||
|
||||
func testDAOForkBlockNewChain(t *testing.T, testnet bool, genesis string, votes [][2]bool, expectBlock *big.Int, expectVote bool) {
|
||||
// Create a temporary data directory to use and inspect later
|
||||
datadir := tmpdir(t)
|
||||
defer os.RemoveAll(datadir)
|
||||
|
||||
// Start a Geth instance with the requested flags set and immediately terminate
|
||||
if genesis != "" {
|
||||
json := filepath.Join(datadir, "genesis.json")
|
||||
if err := ioutil.WriteFile(json, []byte(genesis), 0600); err != nil {
|
||||
t.Fatalf("failed to write genesis file: %v", err)
|
||||
}
|
||||
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
|
||||
}
|
||||
for _, vote := range votes {
|
||||
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
|
||||
if testnet {
|
||||
args = append(args, "--testnet")
|
||||
}
|
||||
if vote[0] {
|
||||
args = append(args, "--support-dao-fork")
|
||||
}
|
||||
if vote[1] {
|
||||
args = append(args, "--oppose-dao-fork")
|
||||
}
|
||||
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
|
||||
geth.cmd.Wait()
|
||||
}
|
||||
// Retrieve the DAO config flag from the database
|
||||
path := filepath.Join(datadir, "chaindata")
|
||||
if testnet && genesis == "" {
|
||||
path = filepath.Join(datadir, "testnet", "chaindata")
|
||||
}
|
||||
db, err := ethdb.NewLDBDatabase(path, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open test database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||
if testnet {
|
||||
genesisHash = common.HexToHash("0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303")
|
||||
}
|
||||
if genesis != "" {
|
||||
genesisHash = daoGenesisHash
|
||||
}
|
||||
config, err := core.GetChainConfig(db, genesisHash)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve chain config: %v", err)
|
||||
}
|
||||
// Validate the DAO hard-fork block number against the expected value
|
||||
if config.DAOForkBlock == nil {
|
||||
if expectBlock != nil {
|
||||
t.Errorf("dao hard-fork block mismatch: have nil, want %v", expectBlock)
|
||||
}
|
||||
} else if expectBlock == nil {
|
||||
t.Errorf("dao hard-fork block mismatch: have %v, want nil", config.DAOForkBlock)
|
||||
} else if config.DAOForkBlock.Cmp(expectBlock) != 0 {
|
||||
t.Errorf("dao hard-fork block mismatch: have %v, want %v", config.DAOForkBlock, expectBlock)
|
||||
}
|
||||
if config.DAOForkSupport != expectVote {
|
||||
t.Errorf("dao hard-fork support mismatch: have %v, want %v", config.DAOForkSupport, expectVote)
|
||||
}
|
||||
}
|
107
cmd/geth/genesis_test.go
Normal file
107
cmd/geth/genesis_test.go
Normal file
@ -0,0 +1,107 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var customGenesisTests = []struct {
|
||||
genesis string
|
||||
query string
|
||||
result string
|
||||
}{
|
||||
// Plain genesis file without anything extra
|
||||
{
|
||||
genesis: `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00"
|
||||
}`,
|
||||
query: "eth.getBlock(0).nonce",
|
||||
result: "0x0000000000000042",
|
||||
},
|
||||
// Genesis file with an empty chain configuration (ensure missing fields work)
|
||||
{
|
||||
genesis: `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {}
|
||||
}`,
|
||||
query: "eth.getBlock(0).nonce",
|
||||
result: "0x0000000000000042",
|
||||
},
|
||||
// Genesis file with specific chain configurations
|
||||
{
|
||||
genesis: `{
|
||||
"alloc" : {},
|
||||
"coinbase" : "0x0000000000000000000000000000000000000000",
|
||||
"difficulty" : "0x20000",
|
||||
"extraData" : "",
|
||||
"gasLimit" : "0x2fefd8",
|
||||
"nonce" : "0x0000000000000042",
|
||||
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"timestamp" : "0x00",
|
||||
"config" : {
|
||||
"homesteadBlock" : 314,
|
||||
"daoForkBlock" : 141,
|
||||
"daoForkSupport" : true
|
||||
},
|
||||
}`,
|
||||
query: "eth.getBlock(0).nonce",
|
||||
result: "0x0000000000000042",
|
||||
},
|
||||
}
|
||||
|
||||
// Tests that initializing Geth with a custom genesis block and chain definitions
|
||||
// work properly.
|
||||
func TestCustomGenesis(t *testing.T) {
|
||||
for i, tt := range customGenesisTests {
|
||||
// Create a temporary data directory to use and inspect later
|
||||
datadir := tmpdir(t)
|
||||
defer os.RemoveAll(datadir)
|
||||
|
||||
// Initialize the data directory with the custom genesis block
|
||||
json := filepath.Join(datadir, "genesis.json")
|
||||
if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil {
|
||||
t.Fatalf("test %d: failed to write genesis file: %v", i, err)
|
||||
}
|
||||
runGeth(t, "--datadir", datadir, "init", json).cmd.Wait()
|
||||
|
||||
// Query the custom genesis block
|
||||
geth := runGeth(t, "--datadir", datadir, "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console")
|
||||
geth.expectRegexp(tt.result)
|
||||
geth.expectExit()
|
||||
}
|
||||
}
|
429
cmd/geth/js.go
429
cmd/geth/js.go
@ -1,429 +0,0 @@
|
||||
// Copyright 2015 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"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/registrar"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/internal/web3ext"
|
||||
re "github.com/ethereum/go-ethereum/jsre"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/peterh/liner"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
var (
|
||||
passwordRegexp = regexp.MustCompile("personal.[nu]")
|
||||
leadingSpace = regexp.MustCompile("^ ")
|
||||
onlyws = regexp.MustCompile("^\\s*$")
|
||||
exit = regexp.MustCompile("^\\s*exit\\s*;*\\s*$")
|
||||
)
|
||||
|
||||
type jsre struct {
|
||||
re *re.JSRE
|
||||
stack *node.Node
|
||||
wait chan *big.Int
|
||||
ps1 string
|
||||
atexit func()
|
||||
corsDomain string
|
||||
client rpc.Client
|
||||
}
|
||||
|
||||
func makeCompleter(re *jsre) liner.WordCompleter {
|
||||
return func(line string, pos int) (head string, completions []string, tail string) {
|
||||
if len(line) == 0 || pos == 0 {
|
||||
return "", nil, ""
|
||||
}
|
||||
// chuck data to relevant part for autocompletion, e.g. in case of nested lines eth.getBalance(eth.coinb<tab><tab>
|
||||
i := 0
|
||||
for i = pos - 1; i > 0; i-- {
|
||||
if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') {
|
||||
continue
|
||||
}
|
||||
if i >= 3 && line[i] == '3' && line[i-3] == 'w' && line[i-2] == 'e' && line[i-1] == 'b' {
|
||||
continue
|
||||
}
|
||||
i += 1
|
||||
break
|
||||
}
|
||||
return line[:i], re.re.CompleteKeywords(line[i:pos]), line[pos:]
|
||||
}
|
||||
}
|
||||
|
||||
func newLightweightJSRE(docRoot string, client rpc.Client, datadir string, interactive bool) *jsre {
|
||||
js := &jsre{ps1: "> "}
|
||||
js.wait = make(chan *big.Int)
|
||||
js.client = client
|
||||
js.re = re.New(docRoot)
|
||||
if err := js.apiBindings(); err != nil {
|
||||
utils.Fatalf("Unable to initialize console - %v", err)
|
||||
}
|
||||
js.setupInput(datadir)
|
||||
return js
|
||||
}
|
||||
|
||||
func newJSRE(stack *node.Node, docRoot, corsDomain string, client rpc.Client, interactive bool) *jsre {
|
||||
js := &jsre{stack: stack, ps1: "> "}
|
||||
// set default cors domain used by startRpc from CLI flag
|
||||
js.corsDomain = corsDomain
|
||||
js.wait = make(chan *big.Int)
|
||||
js.client = client
|
||||
js.re = re.New(docRoot)
|
||||
if err := js.apiBindings(); err != nil {
|
||||
utils.Fatalf("Unable to connect - %v", err)
|
||||
}
|
||||
js.setupInput(stack.DataDir())
|
||||
return js
|
||||
}
|
||||
|
||||
func (self *jsre) setupInput(datadir string) {
|
||||
self.withHistory(datadir, func(hist *os.File) { utils.Stdin.ReadHistory(hist) })
|
||||
utils.Stdin.SetCtrlCAborts(true)
|
||||
utils.Stdin.SetWordCompleter(makeCompleter(self))
|
||||
utils.Stdin.SetTabCompletionStyle(liner.TabPrints)
|
||||
self.atexit = func() {
|
||||
self.withHistory(datadir, func(hist *os.File) {
|
||||
hist.Truncate(0)
|
||||
utils.Stdin.WriteHistory(hist)
|
||||
})
|
||||
utils.Stdin.Close()
|
||||
close(self.wait)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *jsre) batch(statement string) {
|
||||
err := self.re.EvalAndPrettyPrint(statement)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("error: %v", err)
|
||||
}
|
||||
|
||||
if self.atexit != nil {
|
||||
self.atexit()
|
||||
}
|
||||
|
||||
self.re.Stop(false)
|
||||
}
|
||||
|
||||
// show summary of current geth instance
|
||||
func (self *jsre) welcome() {
|
||||
self.re.Run(`
|
||||
(function () {
|
||||
console.log('instance: ' + web3.version.node);
|
||||
console.log("coinbase: " + eth.coinbase);
|
||||
var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp;
|
||||
console.log("at block: " + eth.blockNumber + " (" + new Date(ts) + ")");
|
||||
console.log(' datadir: ' + admin.datadir);
|
||||
})();
|
||||
`)
|
||||
if modules, err := self.supportedApis(); err == nil {
|
||||
loadedModules := make([]string, 0)
|
||||
for api, version := range modules {
|
||||
loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version))
|
||||
}
|
||||
sort.Strings(loadedModules)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *jsre) supportedApis() (map[string]string, error) {
|
||||
return self.client.SupportedModules()
|
||||
}
|
||||
|
||||
func (js *jsre) apiBindings() error {
|
||||
apis, err := js.supportedApis()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiNames := make([]string, 0, len(apis))
|
||||
for a, _ := range apis {
|
||||
apiNames = append(apiNames, a)
|
||||
}
|
||||
|
||||
jeth := utils.NewJeth(js.re, js.client)
|
||||
js.re.Set("jeth", struct{}{})
|
||||
t, _ := js.re.Get("jeth")
|
||||
jethObj := t.Object()
|
||||
|
||||
jethObj.Set("send", jeth.Send)
|
||||
jethObj.Set("sendAsync", jeth.Send)
|
||||
|
||||
err = js.re.Compile("bignumber.js", re.BigNumber_JS)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error loading bignumber.js: %v", err)
|
||||
}
|
||||
|
||||
err = js.re.Compile("web3.js", re.Web3_JS)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error loading web3.js: %v", err)
|
||||
}
|
||||
|
||||
_, err = js.re.Run("var Web3 = require('web3');")
|
||||
if err != nil {
|
||||
utils.Fatalf("Error requiring web3: %v", err)
|
||||
}
|
||||
|
||||
_, err = js.re.Run("var web3 = new Web3(jeth);")
|
||||
if err != nil {
|
||||
utils.Fatalf("Error setting web3 provider: %v", err)
|
||||
}
|
||||
|
||||
// load only supported API's in javascript runtime
|
||||
shortcuts := "var eth = web3.eth; var personal = web3.personal; "
|
||||
for _, apiName := range apiNames {
|
||||
if apiName == "web3" || apiName == "rpc" {
|
||||
continue // manually mapped or ignore
|
||||
}
|
||||
|
||||
if jsFile, ok := web3ext.Modules[apiName]; ok {
|
||||
if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), jsFile); err == nil {
|
||||
shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName)
|
||||
} else {
|
||||
utils.Fatalf("Error loading %s.js: %v", apiName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = js.re.Run(shortcuts)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error setting namespaces: %v", err)
|
||||
}
|
||||
|
||||
js.re.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
|
||||
|
||||
// overrule some of the methods that require password as input and ask for it interactively
|
||||
p, err := js.re.Get("personal")
|
||||
if err != nil {
|
||||
fmt.Println("Unable to overrule sensitive methods in personal module")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Override the unlockAccount and newAccount methods on the personal object since these require user interaction.
|
||||
// Assign the jeth.unlockAccount and jeth.newAccount in the jsre the original web3 callbacks. These will be called
|
||||
// by the jeth.* methods after they got the password from the user and send the original web3 request to the backend.
|
||||
if persObj := p.Object(); persObj != nil { // make sure the personal api is enabled over the interface
|
||||
js.re.Run(`jeth.unlockAccount = personal.unlockAccount;`)
|
||||
persObj.Set("unlockAccount", jeth.UnlockAccount)
|
||||
js.re.Run(`jeth.newAccount = personal.newAccount;`)
|
||||
persObj.Set("newAccount", jeth.NewAccount)
|
||||
}
|
||||
|
||||
// The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
|
||||
// Bind these if the admin module is available.
|
||||
if a, err := js.re.Get("admin"); err == nil {
|
||||
if adminObj := a.Object(); adminObj != nil {
|
||||
adminObj.Set("sleepBlocks", jeth.SleepBlocks)
|
||||
adminObj.Set("sleep", jeth.Sleep)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *jsre) AskPassword() (string, bool) {
|
||||
pass, err := utils.Stdin.PasswordPrompt("Passphrase: ")
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return pass, true
|
||||
}
|
||||
|
||||
func (self *jsre) ConfirmTransaction(tx string) bool {
|
||||
// Retrieve the Ethereum instance from the node
|
||||
var ethereum *eth.Ethereum
|
||||
if err := self.stack.Service(ðereum); err != nil {
|
||||
return false
|
||||
}
|
||||
// If natspec is enabled, ask for permission
|
||||
if ethereum.NatSpec && false /* disabled for now */ {
|
||||
// notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient())
|
||||
// fmt.Println(notice)
|
||||
// answer, _ := self.Prompt("Confirm Transaction [y/n]")
|
||||
// return strings.HasPrefix(strings.Trim(answer, " "), "y")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *jsre) UnlockAccount(addr []byte) bool {
|
||||
fmt.Printf("Please unlock account %x.\n", addr)
|
||||
pass, err := utils.Stdin.PasswordPrompt("Passphrase: ")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// TODO: allow retry
|
||||
var ethereum *eth.Ethereum
|
||||
if err := self.stack.Service(ðereum); err != nil {
|
||||
return false
|
||||
}
|
||||
a := accounts.Account{Address: common.BytesToAddress(addr)}
|
||||
if err := ethereum.AccountManager().Unlock(a, pass); err != nil {
|
||||
return false
|
||||
} else {
|
||||
fmt.Println("Account is now unlocked for this session.")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// preloadJSFiles loads JS files that the user has specified with ctx.PreLoadJSFlag into
|
||||
// the JSRE. If not all files could be loaded it will return an error describing the error.
|
||||
func (self *jsre) preloadJSFiles(ctx *cli.Context) error {
|
||||
if ctx.GlobalString(utils.PreLoadJSFlag.Name) != "" {
|
||||
assetPath := ctx.GlobalString(utils.JSpathFlag.Name)
|
||||
jsFiles := strings.Split(ctx.GlobalString(utils.PreLoadJSFlag.Name), ",")
|
||||
for _, file := range jsFiles {
|
||||
filename := common.AbsolutePath(assetPath, strings.TrimSpace(file))
|
||||
if err := self.re.Exec(filename); err != nil {
|
||||
return fmt.Errorf("%s: %v", file, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exec executes the JS file with the given filename and stops the JSRE
|
||||
func (self *jsre) exec(filename string) error {
|
||||
if err := self.re.Exec(filename); err != nil {
|
||||
self.re.Stop(false)
|
||||
return fmt.Errorf("Javascript Error: %v", err)
|
||||
}
|
||||
self.re.Stop(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *jsre) interactive() {
|
||||
// Read input lines.
|
||||
prompt := make(chan string)
|
||||
inputln := make(chan string)
|
||||
go func() {
|
||||
defer close(inputln)
|
||||
for {
|
||||
line, err := utils.Stdin.Prompt(<-prompt)
|
||||
if err != nil {
|
||||
if err == liner.ErrPromptAborted { // ctrl-C
|
||||
self.resetPrompt()
|
||||
inputln <- ""
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
inputln <- line
|
||||
}
|
||||
}()
|
||||
// Wait for Ctrl-C, too.
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt)
|
||||
|
||||
defer func() {
|
||||
if self.atexit != nil {
|
||||
self.atexit()
|
||||
}
|
||||
self.re.Stop(false)
|
||||
}()
|
||||
for {
|
||||
prompt <- self.ps1
|
||||
select {
|
||||
case <-sig:
|
||||
fmt.Println("caught interrupt, exiting")
|
||||
return
|
||||
case input, ok := <-inputln:
|
||||
if !ok || indentCount <= 0 && exit.MatchString(input) {
|
||||
return
|
||||
}
|
||||
if onlyws.MatchString(input) {
|
||||
continue
|
||||
}
|
||||
str += input + "\n"
|
||||
self.setIndent()
|
||||
if indentCount <= 0 {
|
||||
if mustLogInHistory(str) {
|
||||
utils.Stdin.AppendHistory(str[:len(str)-1])
|
||||
}
|
||||
self.parseInput(str)
|
||||
str = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustLogInHistory(input string) bool {
|
||||
return len(input) == 0 ||
|
||||
passwordRegexp.MatchString(input) ||
|
||||
!leadingSpace.MatchString(input)
|
||||
}
|
||||
|
||||
func (self *jsre) withHistory(datadir string, op func(*os.File)) {
|
||||
hist, err := os.OpenFile(filepath.Join(datadir, "history"), os.O_RDWR|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to open history file: %v\n", err)
|
||||
return
|
||||
}
|
||||
op(hist)
|
||||
hist.Close()
|
||||
}
|
||||
|
||||
func (self *jsre) parseInput(code string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("[native] error", r)
|
||||
}
|
||||
}()
|
||||
if err := self.re.EvalAndPrettyPrint(code); err != nil {
|
||||
if ottoErr, ok := err.(*otto.Error); ok {
|
||||
fmt.Println(ottoErr.String())
|
||||
} else {
|
||||
fmt.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var indentCount = 0
|
||||
var str = ""
|
||||
|
||||
func (self *jsre) resetPrompt() {
|
||||
indentCount = 0
|
||||
str = ""
|
||||
self.ps1 = "> "
|
||||
}
|
||||
|
||||
func (self *jsre) setIndent() {
|
||||
open := strings.Count(str, "{")
|
||||
open += strings.Count(str, "(")
|
||||
closed := strings.Count(str, "}")
|
||||
closed += strings.Count(str, ")")
|
||||
indentCount = open - closed
|
||||
if indentCount <= 0 {
|
||||
self.ps1 = "> "
|
||||
} else {
|
||||
self.ps1 = strings.Join(make([]string, indentCount*2), "..")
|
||||
self.ps1 += " "
|
||||
}
|
||||
}
|
@ -1,500 +0,0 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/compiler"
|
||||
"github.com/ethereum/go-ethereum/common/httpclient"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
)
|
||||
|
||||
const (
|
||||
testSolcPath = ""
|
||||
solcVersion = "0.9.23"
|
||||
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||
testBalance = "10000000000000000000"
|
||||
// of empty string
|
||||
testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
|
||||
)
|
||||
|
||||
var (
|
||||
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
|
||||
testNodeKey, _ = crypto.HexToECDSA("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")
|
||||
testAccount, _ = crypto.HexToECDSA("e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674")
|
||||
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
|
||||
)
|
||||
|
||||
type testjethre struct {
|
||||
*jsre
|
||||
lastConfirm string
|
||||
client *httpclient.HTTPClient
|
||||
}
|
||||
|
||||
// Temporary disabled while natspec hasn't been migrated
|
||||
//func (self *testjethre) ConfirmTransaction(tx string) bool {
|
||||
// var ethereum *eth.Ethereum
|
||||
// self.stack.Service(ðereum)
|
||||
//
|
||||
// if ethereum.NatSpec {
|
||||
// self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
|
||||
// }
|
||||
// return true
|
||||
//}
|
||||
|
||||
func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) {
|
||||
return testREPL(t, nil)
|
||||
}
|
||||
|
||||
func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *node.Node) {
|
||||
tmp, err := ioutil.TempDir("", "geth-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create a networkless protocol stack
|
||||
stack, err := node.New(&node.Config{DataDir: tmp, PrivateKey: testNodeKey, Name: "test", NoDiscovery: true})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create node: %v", err)
|
||||
}
|
||||
// Initialize and register the Ethereum protocol
|
||||
accman := accounts.NewPlaintextManager(filepath.Join(tmp, "keystore"))
|
||||
db, _ := ethdb.NewMemDatabase()
|
||||
core.WriteGenesisBlockForTesting(db, core.GenesisAccount{
|
||||
Address: common.HexToAddress(testAddress),
|
||||
Balance: common.String2Big(testBalance),
|
||||
})
|
||||
ethConf := ð.Config{
|
||||
ChainConfig: &core.ChainConfig{HomesteadBlock: new(big.Int)},
|
||||
TestGenesisState: db,
|
||||
AccountManager: accman,
|
||||
DocRoot: "/",
|
||||
SolcPath: testSolcPath,
|
||||
PowTest: true,
|
||||
}
|
||||
if config != nil {
|
||||
config(ethConf)
|
||||
}
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return eth.New(ctx, ethConf)
|
||||
}); err != nil {
|
||||
t.Fatalf("failed to register ethereum protocol: %v", err)
|
||||
}
|
||||
// Initialize all the keys for testing
|
||||
a, err := accman.ImportECDSA(testAccount, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := accman.Unlock(a, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Start the node and assemble the REPL tester
|
||||
if err := stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start test stack: %v", err)
|
||||
}
|
||||
var ethereum *eth.Ethereum
|
||||
stack.Service(ðereum)
|
||||
|
||||
assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
|
||||
client, err := stack.Attach()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to attach to node: %v", err)
|
||||
}
|
||||
tf := &testjethre{client: ethereum.HTTPClient()}
|
||||
repl := newJSRE(stack, assetPath, "", client, false)
|
||||
tf.jsre = repl
|
||||
return tmp, tf, stack
|
||||
}
|
||||
|
||||
func TestNodeInfo(t *testing.T) {
|
||||
t.Skip("broken after p2p update")
|
||||
tmp, repl, ethereum := testJEthRE(t)
|
||||
defer ethereum.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
want := `{"DiscPort":0,"IP":"0.0.0.0","ListenAddr":"","Name":"test","NodeID":"4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5","NodeUrl":"enode://4cb2fc32924e94277bf94b5e4c983beedb2eabd5a0bc941db32202735c6625d020ca14a5963d1738af43b6ac0a711d61b1a06de931a499fe2aa0b1a132a902b5@0.0.0.0:0","TCPPort":0,"Td":"131072"}`
|
||||
checkEvalJSON(t, repl, `admin.nodeInfo`, want)
|
||||
}
|
||||
|
||||
func TestAccounts(t *testing.T) {
|
||||
tmp, repl, node := testJEthRE(t)
|
||||
defer node.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
|
||||
checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`)
|
||||
val, err := repl.re.Run(`jeth.newAccount("password")`)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
addr := val.String()
|
||||
if !regexp.MustCompile(`0x[0-9a-f]{40}`).MatchString(addr) {
|
||||
t.Errorf("address not hex: %q", addr)
|
||||
}
|
||||
|
||||
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`","`+addr+`"]`)
|
||||
|
||||
}
|
||||
|
||||
func TestBlockChain(t *testing.T) {
|
||||
tmp, repl, node := testJEthRE(t)
|
||||
defer node.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
// get current block dump before export/import.
|
||||
val, err := repl.re.Run("JSON.stringify(debug.dumpBlock(eth.blockNumber))")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
beforeExport := val.String()
|
||||
|
||||
// do the export
|
||||
extmp, err := ioutil.TempDir("", "geth-test-export")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(extmp)
|
||||
tmpfile := filepath.Join(extmp, "export.chain")
|
||||
tmpfileq := strconv.Quote(tmpfile)
|
||||
|
||||
var ethereum *eth.Ethereum
|
||||
node.Service(ðereum)
|
||||
ethereum.BlockChain().Reset()
|
||||
|
||||
checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`)
|
||||
if _, err := os.Stat(tmpfile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check import, verify that dumpBlock gives the same result.
|
||||
checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`)
|
||||
checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport)
|
||||
}
|
||||
|
||||
func TestMining(t *testing.T) {
|
||||
tmp, repl, node := testJEthRE(t)
|
||||
defer node.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
checkEvalJSON(t, repl, `eth.mining`, `false`)
|
||||
}
|
||||
|
||||
func TestRPC(t *testing.T) {
|
||||
tmp, repl, node := testJEthRE(t)
|
||||
defer node.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
checkEvalJSON(t, repl, `admin.startRPC("127.0.0.1", 5004, "*", "web3,eth,net")`, `true`)
|
||||
}
|
||||
|
||||
func TestCheckTestAccountBalance(t *testing.T) {
|
||||
t.Skip() // i don't think it tests the correct behaviour here. it's actually testing
|
||||
// internals which shouldn't be tested. This now fails because of a change in the core
|
||||
// and i have no means to fix this, sorry - @obscuren
|
||||
tmp, repl, node := testJEthRE(t)
|
||||
defer node.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
repl.re.Run(`primary = "` + testAddress + `"`)
|
||||
checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`)
|
||||
}
|
||||
|
||||
func TestSignature(t *testing.T) {
|
||||
tmp, repl, node := testJEthRE(t)
|
||||
defer node.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
val, err := repl.re.Run(`eth.sign("` + testAddress + `", "` + testHash + `")`)
|
||||
|
||||
// This is a very preliminary test, lacking actual signature verification
|
||||
if err != nil {
|
||||
t.Errorf("Error running js: %v", err)
|
||||
return
|
||||
}
|
||||
output := val.String()
|
||||
t.Logf("Output: %v", output)
|
||||
|
||||
regex := regexp.MustCompile(`^0x[0-9a-f]{130}$`)
|
||||
if !regex.MatchString(output) {
|
||||
t.Errorf("Signature is not 65 bytes represented in hexadecimal.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestContract(t *testing.T) {
|
||||
t.Skip("contract testing is implemented with mining in ethash test mode. This takes about 7seconds to run. Unskip and run on demand")
|
||||
coinbase := common.HexToAddress(testAddress)
|
||||
tmp, repl, ethereum := testREPL(t, func(conf *eth.Config) {
|
||||
conf.Etherbase = coinbase
|
||||
conf.PowTest = true
|
||||
})
|
||||
if err := ethereum.Start(); err != nil {
|
||||
t.Errorf("error starting ethereum: %v", err)
|
||||
return
|
||||
}
|
||||
defer ethereum.Stop()
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// Temporary disabled while registrar isn't migrated
|
||||
//reg := registrar.New(repl.xeth)
|
||||
//_, err := reg.SetGlobalRegistrar("", coinbase)
|
||||
//if err != nil {
|
||||
// t.Errorf("error setting HashReg: %v", err)
|
||||
//}
|
||||
//_, err = reg.SetHashReg("", coinbase)
|
||||
//if err != nil {
|
||||
// t.Errorf("error setting HashReg: %v", err)
|
||||
//}
|
||||
//_, err = reg.SetUrlHint("", coinbase)
|
||||
//if err != nil {
|
||||
// t.Errorf("error setting HashReg: %v", err)
|
||||
//}
|
||||
/* TODO:
|
||||
* lookup receipt and contract addresses by tx hash
|
||||
* name registration for HashReg and UrlHint addresses
|
||||
* mine those transactions
|
||||
* then set once more SetHashReg SetUrlHint
|
||||
*/
|
||||
|
||||
source := `contract test {\n` +
|
||||
" /// @notice Will multiply `a` by 7." + `\n` +
|
||||
` function multiply(uint a) returns(uint d) {\n` +
|
||||
` return a * 7;\n` +
|
||||
` }\n` +
|
||||
`}\n`
|
||||
|
||||
if checkEvalJSON(t, repl, `admin.stopNatSpec()`, `true`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
contractInfo, err := ioutil.ReadFile("info_test.json")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`) != nil {
|
||||
return
|
||||
}
|
||||
if checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// if solc is found with right version, test it, otherwise read from file
|
||||
sol, err := compiler.New("")
|
||||
if err != nil {
|
||||
t.Logf("solc not found: mocking contract compilation step")
|
||||
} else if sol.Version() != solcVersion {
|
||||
t.Logf("WARNING: solc different version found (%v, test written for %v, may need to update)", sol.Version(), solcVersion)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
info, err := ioutil.ReadFile("info_test.json")
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
_, err = repl.re.Run(`contract = JSON.parse(` + strconv.Quote(string(info)) + `)`)
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
} else {
|
||||
if checkEvalJSON(t, repl, `contract = eth.compile.solidity(source).test`, string(contractInfo)) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if checkEvalJSON(t, repl, `contract.code`, `"0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056"`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if checkEvalJSON(
|
||||
t, repl,
|
||||
`contractaddress = eth.sendTransaction({from: primary, data: contract.code})`,
|
||||
`"0x46d69d55c3c4b86a924a92c9fc4720bb7bce1d74"`,
|
||||
) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !processTxs(repl, t, 8) {
|
||||
return
|
||||
}
|
||||
|
||||
callSetup := `abiDef = JSON.parse('[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]');
|
||||
Multiply7 = eth.contract(abiDef);
|
||||
multiply7 = Multiply7.at(contractaddress);
|
||||
`
|
||||
_, err = repl.re.Run(callSetup)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error setting up contract, got %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
expNotice := ""
|
||||
if repl.lastConfirm != expNotice {
|
||||
t.Errorf("incorrect confirmation message: expected %v, got %v", expNotice, repl.lastConfirm)
|
||||
return
|
||||
}
|
||||
|
||||
if checkEvalJSON(t, repl, `admin.startNatSpec()`, `true`) != nil {
|
||||
return
|
||||
}
|
||||
if checkEvalJSON(t, repl, `multiply7.multiply.sendTransaction(6, { from: primary })`, `"0x4ef9088431a8033e4580d00e4eb2487275e031ff4163c7529df0ef45af17857b"`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !processTxs(repl, t, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
expNotice = `About to submit transaction (no NatSpec info found for contract: content hash not found for '0x87e2802265838c7f14bb69eecd2112911af6767907a702eeaa445239fb20711b'): {"params":[{"to":"0x46d69d55c3c4b86a924a92c9fc4720bb7bce1d74","data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}]}`
|
||||
if repl.lastConfirm != expNotice {
|
||||
t.Errorf("incorrect confirmation message: expected\n%v, got\n%v", expNotice, repl.lastConfirm)
|
||||
return
|
||||
}
|
||||
|
||||
var contentHash = `"0x86d2b7cf1e72e9a7a3f8d96601f0151742a2f780f1526414304fbe413dc7f9bd"`
|
||||
if sol != nil && solcVersion != sol.Version() {
|
||||
modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`))
|
||||
fmt.Printf("modified contractinfo:\n%s\n", modContractInfo)
|
||||
contentHash = `"` + common.ToHex(crypto.Keccak256([]byte(modContractInfo))) + `"`
|
||||
}
|
||||
if checkEvalJSON(t, repl, `filename = "/tmp/info.json"`, `"/tmp/info.json"`) != nil {
|
||||
return
|
||||
}
|
||||
if checkEvalJSON(t, repl, `contentHash = admin.saveInfo(contract.info, filename)`, contentHash) != nil {
|
||||
return
|
||||
}
|
||||
if checkEvalJSON(t, repl, `admin.register(primary, contractaddress, contentHash)`, `true`) != nil {
|
||||
return
|
||||
}
|
||||
if checkEvalJSON(t, repl, `admin.registerUrl(primary, contentHash, "file://"+filename)`, `true`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if checkEvalJSON(t, repl, `admin.startNatSpec()`, `true`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !processTxs(repl, t, 3) {
|
||||
return
|
||||
}
|
||||
|
||||
if checkEvalJSON(t, repl, `multiply7.multiply.sendTransaction(6, { from: primary })`, `"0x66d7635c12ad0b231e66da2f987ca3dfdca58ffe49c6442aa55960858103fd0c"`) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !processTxs(repl, t, 1) {
|
||||
return
|
||||
}
|
||||
|
||||
expNotice = "Will multiply 6 by 7."
|
||||
if repl.lastConfirm != expNotice {
|
||||
t.Errorf("incorrect confirmation message: expected\n%v, got\n%v", expNotice, repl.lastConfirm)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func pendingTransactions(repl *testjethre, t *testing.T) (txc int64, err error) {
|
||||
var ethereum *eth.Ethereum
|
||||
repl.stack.Service(ðereum)
|
||||
|
||||
txs := ethereum.TxPool().GetTransactions()
|
||||
return int64(len(txs)), nil
|
||||
}
|
||||
|
||||
func processTxs(repl *testjethre, t *testing.T, expTxc int) bool {
|
||||
var txc int64
|
||||
var err error
|
||||
for i := 0; i < 50; i++ {
|
||||
txc, err = pendingTransactions(repl, t)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error checking pending transactions: %v", err)
|
||||
return false
|
||||
}
|
||||
if expTxc < int(txc) {
|
||||
t.Errorf("too many pending transactions: expected %v, got %v", expTxc, txc)
|
||||
return false
|
||||
} else if expTxc == int(txc) {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
if int(txc) != expTxc {
|
||||
t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc)
|
||||
return false
|
||||
}
|
||||
var ethereum *eth.Ethereum
|
||||
repl.stack.Service(ðereum)
|
||||
|
||||
err = ethereum.StartMining(runtime.NumCPU(), "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error mining: %v", err)
|
||||
return false
|
||||
}
|
||||
defer ethereum.StopMining()
|
||||
|
||||
timer := time.NewTimer(100 * time.Second)
|
||||
blockNr := ethereum.BlockChain().CurrentBlock().Number()
|
||||
height := new(big.Int).Add(blockNr, big.NewInt(1))
|
||||
repl.wait <- height
|
||||
select {
|
||||
case <-timer.C:
|
||||
// if times out make sure the xeth loop does not block
|
||||
go func() {
|
||||
select {
|
||||
case repl.wait <- nil:
|
||||
case <-repl.wait:
|
||||
}
|
||||
}()
|
||||
case <-repl.wait:
|
||||
}
|
||||
txc, err = pendingTransactions(repl, t)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error checking pending transactions: %v", err)
|
||||
return false
|
||||
}
|
||||
if txc != 0 {
|
||||
t.Errorf("%d trasactions were not mined", txc)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func checkEvalJSON(t *testing.T, re *testjethre, expr, want string) error {
|
||||
val, err := re.re.Run("JSON.stringify(" + expr + ")")
|
||||
if err == nil && val.String() != want {
|
||||
err = fmt.Errorf("Output mismatch for `%s`:\ngot: %s\nwant: %s", expr, val.String(), want)
|
||||
}
|
||||
if err != nil {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
file = filepath.Base(file)
|
||||
fmt.Printf("\t%s:%d: %v\n", file, line, err)
|
||||
t.Fail()
|
||||
}
|
||||
return err
|
||||
}
|
215
cmd/geth/main.go
215
cmd/geth/main.go
@ -18,6 +18,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -27,10 +28,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/ethash"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
@ -40,31 +41,49 @@ import (
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/release"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientIdentifier = "Geth"
|
||||
Version = "1.4.0-rc"
|
||||
VersionMajor = 1
|
||||
VersionMinor = 4
|
||||
VersionPatch = 0
|
||||
clientIdentifier = "Geth" // Client identifier to advertise over the network
|
||||
versionMajor = 1 // Major version component of the current release
|
||||
versionMinor = 4 // Minor version component of the current release
|
||||
versionPatch = 13 // Patch version component of the current release
|
||||
versionMeta = "stable" // Version metadata to append to the version string
|
||||
|
||||
versionOracle = "0xfa7b9770ca4cb04296cac84f37736d4041251cdf" // Ethereum address of the Geth release oracle
|
||||
)
|
||||
|
||||
var (
|
||||
gitCommit string // set via linker flagg
|
||||
nodeNameVersion string
|
||||
app *cli.App
|
||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||
verString string // Combined textual representation of all the version components
|
||||
relConfig release.Config // Structured version information and release oracle config
|
||||
app *cli.App
|
||||
)
|
||||
|
||||
func init() {
|
||||
if gitCommit == "" {
|
||||
nodeNameVersion = Version
|
||||
} else {
|
||||
nodeNameVersion = Version + "-" + gitCommit[:8]
|
||||
// Construct the textual version string from the individual components
|
||||
verString = fmt.Sprintf("%d.%d.%d", versionMajor, versionMinor, versionPatch)
|
||||
if versionMeta != "" {
|
||||
verString += "-" + versionMeta
|
||||
}
|
||||
if gitCommit != "" {
|
||||
verString += "-" + gitCommit[:8]
|
||||
}
|
||||
// Construct the version release oracle configuration
|
||||
relConfig.Oracle = common.HexToAddress(versionOracle)
|
||||
|
||||
app = utils.NewApp(Version, "the go-ethereum command line interface")
|
||||
relConfig.Major = uint32(versionMajor)
|
||||
relConfig.Minor = uint32(versionMinor)
|
||||
relConfig.Patch = uint32(versionPatch)
|
||||
|
||||
commit, _ := hex.DecodeString(gitCommit)
|
||||
copy(relConfig.Commit[:], commit)
|
||||
|
||||
// Initialize the CLI app and start Geth
|
||||
app = utils.NewApp(verString, "the go-ethereum command line interface")
|
||||
app.Action = geth
|
||||
app.HideVersion = true // we have a command to print the version
|
||||
app.Commands = []cli.Command{
|
||||
@ -76,6 +95,9 @@ func init() {
|
||||
monitorCommand,
|
||||
accountCommand,
|
||||
walletCommand,
|
||||
consoleCommand,
|
||||
attachCommand,
|
||||
javascriptCommand,
|
||||
{
|
||||
Action: makedag,
|
||||
Name: "makedag",
|
||||
@ -119,36 +141,6 @@ The output of this command is supposed to be machine-readable.
|
||||
The init command initialises a new genesis block and definition for the network.
|
||||
This is a destructive action and changes the network in which you will be
|
||||
participating.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: console,
|
||||
Name: "console",
|
||||
Usage: `Geth Console: interactive JavaScript environment`,
|
||||
Description: `
|
||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
||||
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: attach,
|
||||
Name: "attach",
|
||||
Usage: `Geth Console: interactive JavaScript environment (connect to node)`,
|
||||
Description: `
|
||||
The Geth console is an interactive shell for the JavaScript runtime environment
|
||||
which exposes a node admin interface as well as the Ðapp JavaScript API.
|
||||
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
|
||||
This command allows to open a console on a running geth node.
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: execScripts,
|
||||
Name: "js",
|
||||
Usage: `executes the given JavaScript files in the Geth JavaScript VM`,
|
||||
Description: `
|
||||
The JavaScript VM exposes a node admin interface as well as the Ðapp
|
||||
JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
|
||||
`,
|
||||
},
|
||||
}
|
||||
@ -157,7 +149,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||
utils.IdentityFlag,
|
||||
utils.UnlockedAccountFlag,
|
||||
utils.PasswordFileFlag,
|
||||
utils.GenesisFileFlag,
|
||||
utils.BootnodesFlag,
|
||||
utils.DataDirFlag,
|
||||
utils.KeyStoreDirFlag,
|
||||
@ -172,6 +163,8 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||
utils.MaxPendingPeersFlag,
|
||||
utils.EtherbaseFlag,
|
||||
utils.GasPriceFlag,
|
||||
utils.SupportDAOFork,
|
||||
utils.OpposeDAOFork,
|
||||
utils.MinerThreadsFlag,
|
||||
utils.MiningEnabledFlag,
|
||||
utils.MiningGPUFlag,
|
||||
@ -195,7 +188,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||
utils.IPCApiFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.ExecFlag,
|
||||
utils.PreLoadJSFlag,
|
||||
utils.PreloadJSFlag,
|
||||
utils.WhisperEnabledFlag,
|
||||
utils.DevModeFlag,
|
||||
utils.TestNetFlag,
|
||||
@ -205,6 +198,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||
utils.NetworkIdFlag,
|
||||
utils.RPCCORSDomainFlag,
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.FakePoWFlag,
|
||||
utils.SolcPathFlag,
|
||||
utils.GpoMinGasPriceFlag,
|
||||
utils.GpoMaxGasPriceFlag,
|
||||
@ -224,20 +218,20 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
// This should be the only place where reporting is enabled
|
||||
// because it is not intended to run while testing.
|
||||
// In addition to this check, bad block reports are sent only
|
||||
// for chains with the main network genesis block and network id 1.
|
||||
eth.EnableBadBlockReporting = true
|
||||
|
||||
utils.SetupNetwork(ctx)
|
||||
|
||||
// Deprecation warning.
|
||||
if ctx.GlobalIsSet(utils.GenesisFileFlag.Name) {
|
||||
common.PrintDepricationWarning("--genesis is deprecated. Switch to use 'geth init /path/to/file'")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
app.After = func(ctx *cli.Context) error {
|
||||
logger.Flush()
|
||||
debug.Exit()
|
||||
utils.Stdin.Close() // Resets terminal mode.
|
||||
console.Stdin.Close() // Resets terminal mode.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -255,7 +249,7 @@ func makeDefaultExtra() []byte {
|
||||
Name string
|
||||
GoVersion string
|
||||
Os string
|
||||
}{uint(VersionMajor<<16 | VersionMinor<<8 | VersionPatch), ClientIdentifier, runtime.Version(), runtime.GOOS}
|
||||
}{uint(versionMajor<<16 | versionMinor<<8 | versionPatch), clientIdentifier, runtime.Version(), runtime.GOOS}
|
||||
extra, err := rlp.EncodeToBytes(clientInfo)
|
||||
if err != nil {
|
||||
glog.V(logger.Warn).Infoln("error setting canonical miner information:", err)
|
||||
@ -272,45 +266,17 @@ func makeDefaultExtra() []byte {
|
||||
// geth is the main entry point into the system if no special subcommand is ran.
|
||||
// It creates a default node based on the command line arguments and runs it in
|
||||
// blocking mode, waiting for it to be shut down.
|
||||
func geth(ctx *cli.Context) {
|
||||
node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
|
||||
func geth(ctx *cli.Context) error {
|
||||
node := utils.MakeSystemNode(clientIdentifier, verString, relConfig, makeDefaultExtra(), ctx)
|
||||
startNode(ctx, node)
|
||||
node.Wait()
|
||||
}
|
||||
|
||||
// attach will connect to a running geth instance attaching a JavaScript console and to it.
|
||||
func attach(ctx *cli.Context) {
|
||||
// attach to a running geth instance
|
||||
client, err := utils.NewRemoteRPCClient(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("Unable to attach to geth: %v", err)
|
||||
}
|
||||
|
||||
repl := newLightweightJSRE(
|
||||
ctx.GlobalString(utils.JSpathFlag.Name),
|
||||
client,
|
||||
ctx.GlobalString(utils.DataDirFlag.Name),
|
||||
true,
|
||||
)
|
||||
|
||||
// preload user defined JS files into the console
|
||||
err = repl.preloadJSFiles(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to preload JS file %v", err)
|
||||
}
|
||||
|
||||
// in case the exec flag holds a JS statement execute it and return
|
||||
if ctx.GlobalString(utils.ExecFlag.Name) != "" {
|
||||
repl.batch(ctx.GlobalString(utils.ExecFlag.Name))
|
||||
} else {
|
||||
repl.welcome()
|
||||
repl.interactive()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initGenesis will initialise the given JSON format genesis file and writes it as
|
||||
// the zero'd block (i.e. genesis) or will fail hard if it can't succeed.
|
||||
func initGenesis(ctx *cli.Context) {
|
||||
func initGenesis(ctx *cli.Context) error {
|
||||
genesisPath := ctx.Args().First()
|
||||
if len(genesisPath) == 0 {
|
||||
utils.Fatalf("must supply path to genesis JSON file")
|
||||
@ -331,62 +297,7 @@ func initGenesis(ctx *cli.Context) {
|
||||
utils.Fatalf("failed to write genesis block: %v", err)
|
||||
}
|
||||
glog.V(logger.Info).Infof("successfully wrote genesis block and/or chain rule set: %x", block.Hash())
|
||||
}
|
||||
|
||||
// console starts a new geth node, attaching a JavaScript console to it at the
|
||||
// same time.
|
||||
func console(ctx *cli.Context) {
|
||||
// Create and start the node based on the CLI flags
|
||||
node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
|
||||
startNode(ctx, node)
|
||||
|
||||
// Attach to the newly started node, and either execute script or become interactive
|
||||
client, err := node.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||
}
|
||||
repl := newJSRE(node,
|
||||
ctx.GlobalString(utils.JSpathFlag.Name),
|
||||
ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
|
||||
client, true)
|
||||
|
||||
// preload user defined JS files into the console
|
||||
err = repl.preloadJSFiles(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to preload JS file %v", err)
|
||||
}
|
||||
|
||||
// in case the exec flag holds a JS statement execute it and return
|
||||
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
|
||||
repl.batch(script)
|
||||
} else {
|
||||
repl.welcome()
|
||||
repl.interactive()
|
||||
}
|
||||
node.Stop()
|
||||
}
|
||||
|
||||
// execScripts starts a new geth node based on the CLI flags, and executes each
|
||||
// of the JavaScript files specified as command arguments.
|
||||
func execScripts(ctx *cli.Context) {
|
||||
// Create and start the node based on the CLI flags
|
||||
node := utils.MakeSystemNode(ClientIdentifier, nodeNameVersion, makeDefaultExtra(), ctx)
|
||||
startNode(ctx, node)
|
||||
|
||||
// Attach to the newly started node and execute the given scripts
|
||||
client, err := node.Attach()
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to attach to the inproc geth: %v", err)
|
||||
}
|
||||
repl := newJSRE(node,
|
||||
ctx.GlobalString(utils.JSpathFlag.Name),
|
||||
ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
|
||||
client, false)
|
||||
|
||||
for _, file := range ctx.Args() {
|
||||
repl.exec(file)
|
||||
}
|
||||
node.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// startNode boots up the system node and all registered protocols, after which
|
||||
@ -418,7 +329,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func makedag(ctx *cli.Context) {
|
||||
func makedag(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
wrongArgs := func() {
|
||||
utils.Fatalf(`Usage: geth makedag <block number> <outputdir>`)
|
||||
@ -445,13 +356,15 @@ func makedag(ctx *cli.Context) {
|
||||
default:
|
||||
wrongArgs()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gpuinfo(ctx *cli.Context) {
|
||||
func gpuinfo(ctx *cli.Context) error {
|
||||
eth.PrintOpenCLDevices()
|
||||
return nil
|
||||
}
|
||||
|
||||
func gpubench(ctx *cli.Context) {
|
||||
func gpubench(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
wrongArgs := func() {
|
||||
utils.Fatalf(`Usage: geth gpubench <gpu number>`)
|
||||
@ -468,18 +381,18 @@ func gpubench(ctx *cli.Context) {
|
||||
default:
|
||||
wrongArgs()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func version(c *cli.Context) {
|
||||
fmt.Println(ClientIdentifier)
|
||||
fmt.Println("Version:", Version)
|
||||
if gitCommit != "" {
|
||||
fmt.Println("Git Commit:", gitCommit)
|
||||
}
|
||||
func version(c *cli.Context) error {
|
||||
fmt.Println(clientIdentifier)
|
||||
fmt.Println("Version:", verString)
|
||||
fmt.Println("Protocol Versions:", eth.ProtocolVersions)
|
||||
fmt.Println("Network Id:", c.GlobalInt(utils.NetworkIdFlag.Name))
|
||||
fmt.Println("Go Version:", runtime.Version())
|
||||
fmt.Println("OS:", runtime.GOOS)
|
||||
fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH"))
|
||||
fmt.Printf("GOROOT=%s\n", runtime.GOROOT())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ import (
|
||||
|
||||
"sort"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/gizak/termui"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -67,7 +67,7 @@ to display multiple metrics simultaneously.
|
||||
)
|
||||
|
||||
// monitor starts a terminal UI based monitoring tool for the requested metrics.
|
||||
func monitor(ctx *cli.Context) {
|
||||
func monitor(ctx *cli.Context) error {
|
||||
var (
|
||||
client rpc.Client
|
||||
err error
|
||||
@ -154,6 +154,7 @@ func monitor(ctx *cli.Context) {
|
||||
}
|
||||
}()
|
||||
termui.Loop()
|
||||
return nil
|
||||
}
|
||||
|
||||
// retrieveMetrics contacts the attached geth node and retrieves the entire set
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -28,6 +27,7 @@ import (
|
||||
"regexp"
|
||||
"sync"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -45,6 +45,7 @@ type testgeth struct {
|
||||
// template variables for expect
|
||||
Datadir string
|
||||
Executable string
|
||||
Etherbase string
|
||||
Func template.FuncMap
|
||||
|
||||
removeDatadir bool
|
||||
@ -57,7 +58,10 @@ type testgeth struct {
|
||||
func init() {
|
||||
// Run the app if we're the child process for runGeth.
|
||||
if os.Getenv("GETH_TEST_CHILD") != "" {
|
||||
app.RunAndExitOnError()
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
@ -67,11 +71,15 @@ func init() {
|
||||
func runGeth(t *testing.T, args ...string) *testgeth {
|
||||
tt := &testgeth{T: t, Executable: os.Args[0]}
|
||||
for i, arg := range args {
|
||||
if arg == "-datadir" || arg == "--datadir" {
|
||||
switch {
|
||||
case arg == "-datadir" || arg == "--datadir":
|
||||
if i < len(args)-1 {
|
||||
tt.Datadir = args[i+1]
|
||||
}
|
||||
break
|
||||
case arg == "-etherbase" || arg == "--etherbase":
|
||||
if i < len(args)-1 {
|
||||
tt.Etherbase = args[i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
if tt.Datadir == "" {
|
||||
|
@ -21,9 +21,9 @@ package main
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// AppHelpTemplate is the test template for the default, global app help topic.
|
||||
@ -68,7 +68,6 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.OlympicFlag,
|
||||
utils.TestNetFlag,
|
||||
utils.DevModeFlag,
|
||||
utils.GenesisFileFlag,
|
||||
utils.IdentityFlag,
|
||||
utils.FastSyncFlag,
|
||||
utils.LightKDFFlag,
|
||||
@ -101,7 +100,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
utils.RPCCORSDomainFlag,
|
||||
utils.JSpathFlag,
|
||||
utils.ExecFlag,
|
||||
utils.PreLoadJSFlag,
|
||||
utils.PreloadJSFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -150,8 +149,11 @@ var AppHelpFlagGroups = []flagGroup{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "LOGGING AND DEBUGGING",
|
||||
Flags: append([]cli.Flag{utils.MetricsEnabledFlag}, debug.Flags...),
|
||||
Name: "LOGGING AND DEBUGGING",
|
||||
Flags: append([]cli.Flag{
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.FakePoWFlag,
|
||||
}, debug.Flags...),
|
||||
},
|
||||
{
|
||||
Name: "EXPERIMENTAL",
|
||||
|
@ -20,9 +20,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// NewRemoteRPCClient returns a RPC client which connects to a running geth instance.
|
||||
|
@ -73,15 +73,13 @@ func StartNode(stack *node.Node) {
|
||||
<-sigc
|
||||
glog.V(logger.Info).Infoln("Got interrupt, shutting down...")
|
||||
go stack.Stop()
|
||||
logger.Flush()
|
||||
for i := 10; i > 0; i-- {
|
||||
<-sigc
|
||||
if i > 1 {
|
||||
glog.V(logger.Info).Infoln("Already shutting down, please be patient.")
|
||||
glog.V(logger.Info).Infoln("Interrupt", i-1, "more times to induce panic.")
|
||||
glog.V(logger.Info).Infof("Already shutting down, interrupt %d more times for panic.", i-1)
|
||||
}
|
||||
}
|
||||
glog.V(logger.Error).Infof("Force quitting: this might not end so well.")
|
||||
debug.Exit() // ensure trace and CPU profile data is flushed.
|
||||
debug.LoudPanic("boom")
|
||||
}()
|
||||
}
|
||||
@ -122,7 +120,7 @@ func ImportChain(chain *core.BlockChain, fn string) error {
|
||||
}
|
||||
}
|
||||
|
||||
glog.Infoln("Importing blockchain", fn)
|
||||
glog.Infoln("Importing blockchain ", fn)
|
||||
fh, err := os.Open(fn)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -184,7 +182,7 @@ func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool {
|
||||
}
|
||||
|
||||
func ExportChain(blockchain *core.BlockChain, fn string) error {
|
||||
glog.Infoln("Exporting blockchain to", fn)
|
||||
glog.Infoln("Exporting blockchain to ", fn)
|
||||
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -193,12 +191,12 @@ func ExportChain(blockchain *core.BlockChain, fn string) error {
|
||||
if err := blockchain.Export(fh); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infoln("Exported blockchain to", fn)
|
||||
glog.Infoln("Exported blockchain to ", fn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, last uint64) error {
|
||||
glog.Infoln("Exporting blockchain to", fn)
|
||||
glog.Infoln("Exporting blockchain to ", fn)
|
||||
// TODO verify mode perms
|
||||
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
@ -208,6 +206,6 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
|
||||
if err := blockchain.ExportN(fh, first, last); err != nil {
|
||||
return err
|
||||
}
|
||||
glog.Infoln("Exported blockchain to", fn)
|
||||
glog.Infoln("Exported blockchain to ", fn)
|
||||
return nil
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// Custom type which is registered in the flags library which cli uses for
|
||||
|
54
cmd/utils/fdlimit_freebsd.go
Normal file
54
cmd/utils/fdlimit_freebsd.go
Normal file
@ -0,0 +1,54 @@
|
||||
// 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 freebsd
|
||||
|
||||
package utils
|
||||
|
||||
import "syscall"
|
||||
|
||||
// This file is largely identical to fdlimit_unix.go,
|
||||
// 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
|
||||
// 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 > int64(max) {
|
||||
limit.Cur = int64(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
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
// 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
|
||||
// +build linux darwin netbsd openbsd solaris
|
||||
|
||||
package utils
|
||||
|
||||
|
@ -30,7 +30,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ethereum/ethash"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -47,8 +46,11 @@ import (
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/pow"
|
||||
"github.com/ethereum/go-ethereum/release"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/whisper"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -124,10 +126,6 @@ var (
|
||||
Name: "dev",
|
||||
Usage: "Developer mode: pre-configured private network with several debugging flags",
|
||||
}
|
||||
GenesisFileFlag = cli.StringFlag{
|
||||
Name: "genesis",
|
||||
Usage: "Insert/overwrite the genesis block (JSON format)",
|
||||
}
|
||||
IdentityFlag = cli.StringFlag{
|
||||
Name: "identity",
|
||||
Usage: "Custom node name",
|
||||
@ -159,6 +157,15 @@ var (
|
||||
Name: "lightkdf",
|
||||
Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength",
|
||||
}
|
||||
// Fork settings
|
||||
SupportDAOFork = cli.BoolFlag{
|
||||
Name: "support-dao-fork",
|
||||
Usage: "Updates the chain rules to support the DAO hard-fork",
|
||||
}
|
||||
OpposeDAOFork = cli.BoolFlag{
|
||||
Name: "oppose-dao-fork",
|
||||
Usage: "Updates the chain rules to oppose the DAO hard-fork",
|
||||
}
|
||||
// Miner settings
|
||||
// TODO: refactor CPU vs GPU mining flags
|
||||
MiningEnabledFlag = cli.BoolFlag{
|
||||
@ -228,6 +235,10 @@ var (
|
||||
Name: metrics.MetricsEnabledFlag,
|
||||
Usage: "Enable metrics collection and reporting",
|
||||
}
|
||||
FakePoWFlag = cli.BoolFlag{
|
||||
Name: "fakepow",
|
||||
Usage: "Disables proof-of-work verification",
|
||||
}
|
||||
|
||||
// RPC settings
|
||||
RPCEnabledFlag = cli.BoolFlag{
|
||||
@ -296,7 +307,7 @@ var (
|
||||
Name: "exec",
|
||||
Usage: "Execute JavaScript statement (only in combination with console/attach)",
|
||||
}
|
||||
PreLoadJSFlag = cli.StringFlag{
|
||||
PreloadJSFlag = cli.StringFlag{
|
||||
Name: "preload",
|
||||
Usage: "Comma separated list of JavaScript files to preload into the console",
|
||||
}
|
||||
@ -528,20 +539,6 @@ func MakeWSRpcHost(ctx *cli.Context) string {
|
||||
return ctx.GlobalString(WSListenAddrFlag.Name)
|
||||
}
|
||||
|
||||
// MakeGenesisBlock loads up a genesis block from an input file specified in the
|
||||
// command line, or returns the empty string if none set.
|
||||
func MakeGenesisBlock(ctx *cli.Context) string {
|
||||
genesis := ctx.GlobalString(GenesisFileFlag.Name)
|
||||
if genesis == "" {
|
||||
return ""
|
||||
}
|
||||
data, err := ioutil.ReadFile(genesis)
|
||||
if err != nil {
|
||||
Fatalf("Failed to load custom genesis file: %v", err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -636,7 +633,7 @@ func MakePasswordList(ctx *cli.Context) []string {
|
||||
|
||||
// MakeSystemNode sets up a local node, configures the services to launch and
|
||||
// assembles the P2P protocol stack.
|
||||
func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.Node {
|
||||
func MakeSystemNode(name, version string, relconf release.Config, extra []byte, ctx *cli.Context) *node.Node {
|
||||
// Avoid conflicting network flags
|
||||
networks, netFlags := 0, []cli.BoolFlag{DevModeFlag, TestNetFlag, OlympicFlag}
|
||||
for _, flag := range netFlags {
|
||||
@ -683,7 +680,6 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
||||
|
||||
ethConf := ð.Config{
|
||||
ChainConfig: MustMakeChainConfig(ctx),
|
||||
Genesis: MakeGenesisBlock(ctx),
|
||||
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
|
||||
BlockChainVersion: ctx.GlobalInt(BlockchainVersionFlag.Name),
|
||||
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
|
||||
@ -716,17 +712,13 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||
ethConf.NetworkId = 1
|
||||
}
|
||||
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
|
||||
ethConf.Genesis = core.OlympicGenesisBlock()
|
||||
}
|
||||
ethConf.Genesis = core.OlympicGenesisBlock()
|
||||
|
||||
case ctx.GlobalBool(TestNetFlag.Name):
|
||||
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
|
||||
ethConf.NetworkId = 2
|
||||
}
|
||||
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
|
||||
ethConf.Genesis = core.TestNetGenesisBlock()
|
||||
}
|
||||
ethConf.Genesis = core.TestNetGenesisBlock()
|
||||
state.StartingNonce = 1048576 // (2**20)
|
||||
|
||||
case ctx.GlobalBool(DevModeFlag.Name):
|
||||
@ -741,9 +733,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
||||
stackConf.ListenAddr = ":0"
|
||||
}
|
||||
// Override the Ethereum protocol configs
|
||||
if !ctx.GlobalIsSet(GenesisFileFlag.Name) {
|
||||
ethConf.Genesis = core.OlympicGenesisBlock()
|
||||
}
|
||||
ethConf.Genesis = core.OlympicGenesisBlock()
|
||||
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
|
||||
ethConf.GasPrice = new(big.Int)
|
||||
}
|
||||
@ -767,7 +757,11 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
||||
Fatalf("Failed to register the Whisper service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return release.NewReleaseService(ctx, relconf)
|
||||
}); err != nil {
|
||||
Fatalf("Failed to register the Geth release oracle service: %v", err)
|
||||
}
|
||||
return stack
|
||||
}
|
||||
|
||||
@ -796,24 +790,62 @@ func MustMakeChainConfig(ctx *cli.Context) *core.ChainConfig {
|
||||
|
||||
// MustMakeChainConfigFromDb reads the chain configuration from the given database.
|
||||
func MustMakeChainConfigFromDb(ctx *cli.Context, db ethdb.Database) *core.ChainConfig {
|
||||
genesis := core.GetBlock(db, core.GetCanonicalHash(db, 0))
|
||||
// If the chain is already initialized, use any existing chain configs
|
||||
config := new(core.ChainConfig)
|
||||
|
||||
genesis := core.GetBlock(db, core.GetCanonicalHash(db, 0))
|
||||
if genesis != nil {
|
||||
// Existing genesis block, use stored config if available.
|
||||
storedConfig, err := core.GetChainConfig(db, genesis.Hash())
|
||||
if err == nil {
|
||||
return storedConfig
|
||||
} else if err != core.ChainConfigNotFoundErr {
|
||||
switch err {
|
||||
case nil:
|
||||
config = storedConfig
|
||||
case core.ChainConfigNotFoundErr:
|
||||
// No configs found, use empty, will populate below
|
||||
default:
|
||||
Fatalf("Could not make chain configuration: %v", err)
|
||||
}
|
||||
}
|
||||
var homesteadBlockNo *big.Int
|
||||
if ctx.GlobalBool(TestNetFlag.Name) {
|
||||
homesteadBlockNo = params.TestNetHomesteadBlock
|
||||
} else {
|
||||
homesteadBlockNo = params.MainNetHomesteadBlock
|
||||
// Set any missing fields due to them being unset or system upgrade
|
||||
if config.HomesteadBlock == nil {
|
||||
if ctx.GlobalBool(TestNetFlag.Name) {
|
||||
config.HomesteadBlock = params.TestNetHomesteadBlock
|
||||
} else {
|
||||
config.HomesteadBlock = params.MainNetHomesteadBlock
|
||||
}
|
||||
}
|
||||
return &core.ChainConfig{HomesteadBlock: homesteadBlockNo}
|
||||
if config.DAOForkBlock == nil {
|
||||
if ctx.GlobalBool(TestNetFlag.Name) {
|
||||
config.DAOForkBlock = params.TestNetDAOForkBlock
|
||||
} else {
|
||||
config.DAOForkBlock = params.MainNetDAOForkBlock
|
||||
}
|
||||
config.DAOForkSupport = true
|
||||
}
|
||||
// Force override any existing configs if explicitly requested
|
||||
switch {
|
||||
case ctx.GlobalBool(SupportDAOFork.Name):
|
||||
config.DAOForkSupport = true
|
||||
case ctx.GlobalBool(OpposeDAOFork.Name):
|
||||
config.DAOForkSupport = false
|
||||
}
|
||||
// Temporarilly display a proper message so the user knows which fork its on
|
||||
if !ctx.GlobalBool(TestNetFlag.Name) && (genesis == nil || genesis.Hash() == common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")) {
|
||||
choice := "SUPPORT"
|
||||
if !config.DAOForkSupport {
|
||||
choice = "OPPOSE"
|
||||
}
|
||||
current := fmt.Sprintf("Geth is currently configured to %s the DAO hard-fork!", choice)
|
||||
howtoswap := fmt.Sprintf("You can change your choice prior to block #%v with --support-dao-fork or --oppose-dao-fork.", config.DAOForkBlock)
|
||||
howtosync := fmt.Sprintf("After the hard-fork block #%v passed, changing chains requires a resync from scratch!", config.DAOForkBlock)
|
||||
separator := strings.Repeat("-", len(howtoswap))
|
||||
|
||||
glog.V(logger.Warn).Info(separator)
|
||||
glog.V(logger.Warn).Info(current)
|
||||
glog.V(logger.Warn).Info(howtoswap)
|
||||
glog.V(logger.Warn).Info(howtosync)
|
||||
glog.V(logger.Warn).Info(separator)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
|
||||
@ -842,13 +874,32 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
|
||||
glog.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
chainConfig := MustMakeChainConfigFromDb(ctx, chainDb)
|
||||
|
||||
var eventMux event.TypeMux
|
||||
chain, err = core.NewBlockChain(chainDb, chainConfig, ethash.New(), &eventMux)
|
||||
pow := pow.PoW(core.FakePow{})
|
||||
if !ctx.GlobalBool(FakePoWFlag.Name) {
|
||||
pow = ethash.New()
|
||||
}
|
||||
chain, err = core.NewBlockChain(chainDb, chainConfig, pow, new(event.TypeMux))
|
||||
if err != nil {
|
||||
Fatalf("Could not start chainmanager: %v", err)
|
||||
}
|
||||
return chain, chainDb
|
||||
}
|
||||
|
||||
// MakeConsolePreloads retrieves the absolute paths for the console JavaScript
|
||||
// scripts to preload before starting.
|
||||
func MakeConsolePreloads(ctx *cli.Context) []string {
|
||||
// Skip preloading if there's nothing to preload
|
||||
if ctx.GlobalString(PreloadJSFlag.Name) == "" {
|
||||
return nil
|
||||
}
|
||||
// Otherwise resolve absolute paths and return them
|
||||
preloads := []string{}
|
||||
|
||||
assets := ctx.GlobalString(JSpathFlag.Name)
|
||||
for _, file := range strings.Split(ctx.GlobalString(PreloadJSFlag.Name), ",") {
|
||||
preloads = append(preloads, common.AbsolutePath(assets, strings.TrimSpace(file)))
|
||||
}
|
||||
return preloads
|
||||
}
|
||||
|
@ -1,98 +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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/peterh/liner"
|
||||
)
|
||||
|
||||
// Holds the stdin line reader.
|
||||
// Only this reader may be used for input because it keeps
|
||||
// an internal buffer.
|
||||
var Stdin = newUserInputReader()
|
||||
|
||||
type userInputReader struct {
|
||||
*liner.State
|
||||
warned bool
|
||||
supported bool
|
||||
normalMode liner.ModeApplier
|
||||
rawMode liner.ModeApplier
|
||||
}
|
||||
|
||||
func newUserInputReader() *userInputReader {
|
||||
r := new(userInputReader)
|
||||
// Get the original mode before calling NewLiner.
|
||||
// This is usually regular "cooked" mode where characters echo.
|
||||
normalMode, _ := liner.TerminalMode()
|
||||
// Turn on liner. It switches to raw mode.
|
||||
r.State = liner.NewLiner()
|
||||
rawMode, err := liner.TerminalMode()
|
||||
if err != nil || !liner.TerminalSupported() {
|
||||
r.supported = false
|
||||
} else {
|
||||
r.supported = true
|
||||
r.normalMode = normalMode
|
||||
r.rawMode = rawMode
|
||||
// Switch back to normal mode while we're not prompting.
|
||||
normalMode.ApplyMode()
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *userInputReader) Prompt(prompt string) (string, error) {
|
||||
if r.supported {
|
||||
r.rawMode.ApplyMode()
|
||||
defer r.normalMode.ApplyMode()
|
||||
} else {
|
||||
// liner tries to be smart about printing the prompt
|
||||
// and doesn't print anything if input is redirected.
|
||||
// Un-smart it by printing the prompt always.
|
||||
fmt.Print(prompt)
|
||||
prompt = ""
|
||||
defer fmt.Println()
|
||||
}
|
||||
return r.State.Prompt(prompt)
|
||||
}
|
||||
|
||||
func (r *userInputReader) PasswordPrompt(prompt string) (passwd string, err error) {
|
||||
if r.supported {
|
||||
r.rawMode.ApplyMode()
|
||||
defer r.normalMode.ApplyMode()
|
||||
return r.State.PasswordPrompt(prompt)
|
||||
}
|
||||
if !r.warned {
|
||||
fmt.Println("!! Unsupported terminal, password will be echoed.")
|
||||
r.warned = true
|
||||
}
|
||||
// Just as in Prompt, handle printing the prompt here instead of relying on liner.
|
||||
fmt.Print(prompt)
|
||||
passwd, err = r.State.Prompt("")
|
||||
fmt.Println()
|
||||
return passwd, err
|
||||
}
|
||||
|
||||
func (r *userInputReader) ConfirmPrompt(prompt string) (bool, error) {
|
||||
prompt = prompt + " [y/N] "
|
||||
input, err := r.Prompt(prompt)
|
||||
if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
@ -1,301 +0,0 @@
|
||||
// Copyright 2015 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/jsre"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
type Jeth struct {
|
||||
re *jsre.JSRE
|
||||
client rpc.Client
|
||||
}
|
||||
|
||||
// NewJeth create a new backend for the JSRE console
|
||||
func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth {
|
||||
return &Jeth{re, client}
|
||||
}
|
||||
|
||||
// err returns an error object for the given error code and message.
|
||||
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
|
||||
m := rpc.JSONErrResponse{
|
||||
Version: "2.0",
|
||||
Id: id,
|
||||
Error: rpc.JSONError{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
|
||||
errObj, _ := json.Marshal(m.Error)
|
||||
errRes, _ := json.Marshal(m)
|
||||
|
||||
call.Otto.Run("ret_error = " + string(errObj))
|
||||
res, _ := call.Otto.Run("ret_response = " + string(errRes))
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre.
|
||||
// It will need the public address for the account to unlock as first argument.
|
||||
// The second argument is an optional string with the password. If not given the user is prompted for the password.
|
||||
// The third argument is an optional integer which specifies for how long the account will be unlocked (in seconds).
|
||||
func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
|
||||
var account, passwd otto.Value
|
||||
duration := otto.NullValue()
|
||||
|
||||
if !call.Argument(0).IsString() {
|
||||
fmt.Println("first argument must be the account to unlock")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
account = call.Argument(0)
|
||||
|
||||
// if password is not given or as null value -> ask user for password
|
||||
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
||||
fmt.Printf("Unlock account %s\n", account)
|
||||
if input, err := Stdin.PasswordPrompt("Passphrase: "); err != nil {
|
||||
throwJSExeception(err.Error())
|
||||
} else {
|
||||
passwd, _ = otto.ToValue(input)
|
||||
}
|
||||
} else {
|
||||
if !call.Argument(1).IsString() {
|
||||
throwJSExeception("password must be a string")
|
||||
}
|
||||
passwd = call.Argument(1)
|
||||
}
|
||||
|
||||
// third argument is the duration how long the account must be unlocked.
|
||||
// verify that its a number.
|
||||
if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() {
|
||||
if !call.Argument(2).IsNumber() {
|
||||
throwJSExeception("unlock duration must be a number")
|
||||
}
|
||||
duration = call.Argument(2)
|
||||
}
|
||||
|
||||
// jeth.unlockAccount will send the request to the backend.
|
||||
if val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration); err == nil {
|
||||
return val
|
||||
} else {
|
||||
throwJSExeception(err.Error())
|
||||
}
|
||||
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
// NewAccount asks the user for the password and than executes the jeth.newAccount callback in the jsre
|
||||
func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
||||
var passwd string
|
||||
if len(call.ArgumentList) == 0 {
|
||||
var err error
|
||||
passwd, err = Stdin.PasswordPrompt("Passphrase: ")
|
||||
if err != nil {
|
||||
return otto.FalseValue()
|
||||
}
|
||||
passwd2, err := Stdin.PasswordPrompt("Repeat passphrase: ")
|
||||
if err != nil {
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
if passwd != passwd2 {
|
||||
fmt.Println("Passphrases don't match")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
} else if len(call.ArgumentList) == 1 && call.Argument(0).IsString() {
|
||||
passwd, _ = call.Argument(0).ToString()
|
||||
} else {
|
||||
fmt.Println("expected 0 or 1 string argument")
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
ret, err := call.Otto.Call("jeth.newAccount", nil, passwd)
|
||||
if err == nil {
|
||||
return ret
|
||||
}
|
||||
fmt.Println(err)
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
// Send will serialize the first argument, send it to the node and returns the response.
|
||||
func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
|
||||
// verify we got a batch request (array) or a single request (object)
|
||||
ro := call.Argument(0).Object()
|
||||
if ro == nil || (ro.Class() != "Array" && ro.Class() != "Object") {
|
||||
throwJSExeception("Internal Error: request must be an object or array")
|
||||
}
|
||||
|
||||
// convert otto vm arguments to go values by JSON serialising and parsing.
|
||||
data, err := call.Otto.Call("JSON.stringify", nil, ro)
|
||||
if err != nil {
|
||||
throwJSExeception(err.Error())
|
||||
}
|
||||
|
||||
jsonreq, _ := data.ToString()
|
||||
|
||||
// parse arguments to JSON rpc requests, either to an array (batch) or to a single request.
|
||||
var reqs []rpc.JSONRequest
|
||||
batch := true
|
||||
if err = json.Unmarshal([]byte(jsonreq), &reqs); err != nil {
|
||||
// single request?
|
||||
reqs = make([]rpc.JSONRequest, 1)
|
||||
if err = json.Unmarshal([]byte(jsonreq), &reqs[0]); err != nil {
|
||||
throwJSExeception("invalid request")
|
||||
}
|
||||
batch = false
|
||||
}
|
||||
|
||||
call.Otto.Set("response_len", len(reqs))
|
||||
call.Otto.Run("var ret_response = new Array(response_len);")
|
||||
|
||||
for i, req := range reqs {
|
||||
if err := self.client.Send(&req); err != nil {
|
||||
return self.err(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
if err = self.client.Recv(&result); err != nil {
|
||||
return self.err(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
|
||||
id, _ := result["id"]
|
||||
jsonver, _ := result["jsonrpc"]
|
||||
|
||||
call.Otto.Set("ret_id", id)
|
||||
call.Otto.Set("ret_jsonrpc", jsonver)
|
||||
call.Otto.Set("response_idx", i)
|
||||
|
||||
// call was successful
|
||||
if res, ok := result["result"]; ok {
|
||||
payload, _ := json.Marshal(res)
|
||||
call.Otto.Set("ret_result", string(payload))
|
||||
response, err = call.Otto.Run(`
|
||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
||||
`)
|
||||
continue
|
||||
}
|
||||
|
||||
// request returned an error
|
||||
if res, ok := result["error"]; ok {
|
||||
payload, _ := json.Marshal(res)
|
||||
call.Otto.Set("ret_result", string(payload))
|
||||
response, err = call.Otto.Run(`
|
||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, error: JSON.parse(ret_result) };
|
||||
`)
|
||||
continue
|
||||
}
|
||||
|
||||
return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64))
|
||||
}
|
||||
|
||||
if !batch {
|
||||
call.Otto.Run("ret_response = ret_response[0];")
|
||||
}
|
||||
|
||||
// if a callback was given execute it.
|
||||
if call.Argument(1).IsObject() {
|
||||
call.Otto.Set("callback", call.Argument(1))
|
||||
call.Otto.Run(`
|
||||
if (Object.prototype.toString.call(callback) == '[object Function]') {
|
||||
callback(null, ret_response);
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error.
|
||||
func throwJSExeception(msg interface{}) otto.Value {
|
||||
p, _ := otto.ToValue(msg)
|
||||
panic(p)
|
||||
}
|
||||
|
||||
// Sleep will halt the console for arg[0] seconds.
|
||||
func (self *Jeth) Sleep(call otto.FunctionCall) (response otto.Value) {
|
||||
if len(call.ArgumentList) >= 1 {
|
||||
if call.Argument(0).IsNumber() {
|
||||
sleep, _ := call.Argument(0).ToInteger()
|
||||
time.Sleep(time.Duration(sleep) * time.Second)
|
||||
return otto.TrueValue()
|
||||
}
|
||||
}
|
||||
return throwJSExeception("usage: sleep(<sleep in seconds>)")
|
||||
}
|
||||
|
||||
// SleepBlocks will wait for a specified number of new blocks or max for a
|
||||
// given of seconds. sleepBlocks(nBlocks[, maxSleep]).
|
||||
func (self *Jeth) SleepBlocks(call otto.FunctionCall) (response otto.Value) {
|
||||
nBlocks := int64(0)
|
||||
maxSleep := int64(9999999999999999) // indefinitely
|
||||
|
||||
nArgs := len(call.ArgumentList)
|
||||
|
||||
if nArgs == 0 {
|
||||
throwJSExeception("usage: sleepBlocks(<n blocks>[, max sleep in seconds])")
|
||||
}
|
||||
|
||||
if nArgs >= 1 {
|
||||
if call.Argument(0).IsNumber() {
|
||||
nBlocks, _ = call.Argument(0).ToInteger()
|
||||
} else {
|
||||
throwJSExeception("expected number as first argument")
|
||||
}
|
||||
}
|
||||
|
||||
if nArgs >= 2 {
|
||||
if call.Argument(1).IsNumber() {
|
||||
maxSleep, _ = call.Argument(1).ToInteger()
|
||||
} else {
|
||||
throwJSExeception("expected number as second argument")
|
||||
}
|
||||
}
|
||||
|
||||
// go through the console, this will allow web3 to call the appropriate
|
||||
// callbacks if a delayed response or notification is received.
|
||||
currentBlockNr := func() int64 {
|
||||
result, err := call.Otto.Run("eth.blockNumber")
|
||||
if err != nil {
|
||||
throwJSExeception(err.Error())
|
||||
}
|
||||
blockNr, err := result.ToInteger()
|
||||
if err != nil {
|
||||
throwJSExeception(err.Error())
|
||||
}
|
||||
return blockNr
|
||||
}
|
||||
|
||||
targetBlockNr := currentBlockNr() + nBlocks
|
||||
deadline := time.Now().Add(time.Duration(maxSleep) * time.Second)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
if currentBlockNr() >= targetBlockNr {
|
||||
return otto.TrueValue()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
|
||||
return otto.FalseValue()
|
||||
}
|
@ -149,7 +149,6 @@ func (sol *Solidity) Compile(source string) (map[string]*Contract, error) {
|
||||
compilerOptions := strings.Join(params, " ")
|
||||
|
||||
cmd := exec.Command(sol.solcPath, params...)
|
||||
cmd.Dir = wd
|
||||
cmd.Stdin = strings.NewReader(source)
|
||||
cmd.Stderr = stderr
|
||||
|
||||
|
@ -167,7 +167,7 @@ func (a Address) MarshalJSON() ([]byte, error) {
|
||||
// Parse address from raw json data
|
||||
func (a *Address) UnmarshalJSON(data []byte) error {
|
||||
if len(data) > 2 && data[0] == '"' && data[len(data)-1] == '"' {
|
||||
data = data[:len(data)-1][1:]
|
||||
data = data[1 : len(data)-1]
|
||||
}
|
||||
|
||||
if len(data) > 2 && data[0] == '0' && data[1] == 'x' {
|
||||
|
@ -16,7 +16,10 @@
|
||||
|
||||
package common
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBytesConversion(t *testing.T) {
|
||||
bytes := []byte{5}
|
||||
@ -47,7 +50,38 @@ func TestHashJsonValidation(t *testing.T) {
|
||||
}
|
||||
for i, test := range tests {
|
||||
if err := h.UnmarshalJSON(append([]byte(test.Prefix), make([]byte, test.Size)...)); err != test.Error {
|
||||
t.Error(i, "expected", test.Error, "got", err)
|
||||
t.Errorf("test #%d: error mismatch: have %v, want %v", i, err, test.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddressUnmarshalJSON(t *testing.T) {
|
||||
var a Address
|
||||
var tests = []struct {
|
||||
Input string
|
||||
ShouldErr bool
|
||||
Output *big.Int
|
||||
}{
|
||||
{"", true, nil},
|
||||
{`""`, true, nil},
|
||||
{`"0x"`, true, nil},
|
||||
{`"0x00"`, true, nil},
|
||||
{`"0xG000000000000000000000000000000000000000"`, true, nil},
|
||||
{`"0x0000000000000000000000000000000000000000"`, false, big.NewInt(0)},
|
||||
{`"0x0000000000000000000000000000000000000010"`, false, big.NewInt(16)},
|
||||
}
|
||||
for i, test := range tests {
|
||||
err := a.UnmarshalJSON([]byte(test.Input))
|
||||
if err != nil && !test.ShouldErr {
|
||||
t.Errorf("test #%d: unexpected error: %v", i, err)
|
||||
}
|
||||
if err == nil {
|
||||
if test.ShouldErr {
|
||||
t.Errorf("test #%d: expected error, got none", i)
|
||||
}
|
||||
if a.Big().Cmp(test.Output) != 0 {
|
||||
t.Errorf("test #%d: address mismatch: have %v, want %v", i, a.Big(), test.Output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
317
console/bridge.go
Normal file
317
console/bridge.go
Normal file
@ -0,0 +1,317 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/logger"
|
||||
"github.com/ethereum/go-ethereum/logger/glog"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
// bridge is a collection of JavaScript utility methods to bride the .js runtime
|
||||
// environment and the Go RPC connection backing the remote method calls.
|
||||
type bridge struct {
|
||||
client rpc.Client // RPC client to execute Ethereum requests through
|
||||
prompter UserPrompter // Input prompter to allow interactive user feedback
|
||||
printer io.Writer // Output writer to serialize any display strings to
|
||||
}
|
||||
|
||||
// newBridge creates a new JavaScript wrapper around an RPC client.
|
||||
func newBridge(client rpc.Client, prompter UserPrompter, printer io.Writer) *bridge {
|
||||
return &bridge{
|
||||
client: client,
|
||||
prompter: prompter,
|
||||
printer: printer,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccount is a wrapper around the personal.newAccount RPC method that uses a
|
||||
// non-echoing password prompt to aquire the passphrase and executes the original
|
||||
// RPC method (saved in jeth.newAccount) with it to actually execute the RPC call.
|
||||
func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
||||
var (
|
||||
password string
|
||||
confirm string
|
||||
err error
|
||||
)
|
||||
switch {
|
||||
// No password was specified, prompt the user for it
|
||||
case len(call.ArgumentList) == 0:
|
||||
if password, err = b.prompter.PromptPassword("Passphrase: "); err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
if confirm, err = b.prompter.PromptPassword("Repeat passphrase: "); err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
if password != confirm {
|
||||
throwJSException("passphrases don't match!")
|
||||
}
|
||||
|
||||
// A single string password was specified, use that
|
||||
case len(call.ArgumentList) == 1 && call.Argument(0).IsString():
|
||||
password, _ = call.Argument(0).ToString()
|
||||
|
||||
// Otherwise fail with some error
|
||||
default:
|
||||
throwJSException("expected 0 or 1 string argument")
|
||||
}
|
||||
// Password aquired, execute the call and return
|
||||
ret, err := call.Otto.Call("jeth.newAccount", nil, password)
|
||||
if err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that
|
||||
// uses a non-echoing password prompt to aquire the passphrase and executes the
|
||||
// original RPC method (saved in jeth.unlockAccount) with it to actually execute
|
||||
// the RPC call.
|
||||
func (b *bridge) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
|
||||
// Make sure we have an account specified to unlock
|
||||
if !call.Argument(0).IsString() {
|
||||
throwJSException("first argument must be the account to unlock")
|
||||
}
|
||||
account := call.Argument(0)
|
||||
|
||||
// If password is not given or is the null value, prompt the user for it
|
||||
var passwd otto.Value
|
||||
|
||||
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
||||
fmt.Fprintf(b.printer, "Unlock account %s\n", account)
|
||||
if input, err := b.prompter.PromptPassword("Passphrase: "); err != nil {
|
||||
throwJSException(err.Error())
|
||||
} else {
|
||||
passwd, _ = otto.ToValue(input)
|
||||
}
|
||||
} else {
|
||||
if !call.Argument(1).IsString() {
|
||||
throwJSException("password must be a string")
|
||||
}
|
||||
passwd = call.Argument(1)
|
||||
}
|
||||
// Third argument is the duration how long the account must be unlocked.
|
||||
duration := otto.NullValue()
|
||||
if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() {
|
||||
if !call.Argument(2).IsNumber() {
|
||||
throwJSException("unlock duration must be a number")
|
||||
}
|
||||
duration = call.Argument(2)
|
||||
}
|
||||
// Send the request to the backend and return
|
||||
val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration)
|
||||
if err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Sleep will block the console for the specified number of seconds.
|
||||
func (b *bridge) Sleep(call otto.FunctionCall) (response otto.Value) {
|
||||
if call.Argument(0).IsNumber() {
|
||||
sleep, _ := call.Argument(0).ToInteger()
|
||||
time.Sleep(time.Duration(sleep) * time.Second)
|
||||
return otto.TrueValue()
|
||||
}
|
||||
return throwJSException("usage: sleep(<number of seconds>)")
|
||||
}
|
||||
|
||||
// SleepBlocks will block the console for a specified number of new blocks optionally
|
||||
// until the given timeout is reached.
|
||||
func (b *bridge) SleepBlocks(call otto.FunctionCall) (response otto.Value) {
|
||||
var (
|
||||
blocks = int64(0)
|
||||
sleep = int64(9999999999999999) // indefinitely
|
||||
)
|
||||
// Parse the input parameters for the sleep
|
||||
nArgs := len(call.ArgumentList)
|
||||
if nArgs == 0 {
|
||||
throwJSException("usage: sleepBlocks(<n blocks>[, max sleep in seconds])")
|
||||
}
|
||||
if nArgs >= 1 {
|
||||
if call.Argument(0).IsNumber() {
|
||||
blocks, _ = call.Argument(0).ToInteger()
|
||||
} else {
|
||||
throwJSException("expected number as first argument")
|
||||
}
|
||||
}
|
||||
if nArgs >= 2 {
|
||||
if call.Argument(1).IsNumber() {
|
||||
sleep, _ = call.Argument(1).ToInteger()
|
||||
} else {
|
||||
throwJSException("expected number as second argument")
|
||||
}
|
||||
}
|
||||
// go through the console, this will allow web3 to call the appropriate
|
||||
// callbacks if a delayed response or notification is received.
|
||||
blockNumber := func() int64 {
|
||||
result, err := call.Otto.Run("eth.blockNumber")
|
||||
if err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
block, err := result.ToInteger()
|
||||
if err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
return block
|
||||
}
|
||||
// Poll the current block number until either it ot a timeout is reached
|
||||
targetBlockNr := blockNumber() + blocks
|
||||
deadline := time.Now().Add(time.Duration(sleep) * time.Second)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
if blockNumber() >= targetBlockNr {
|
||||
return otto.TrueValue()
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
return otto.FalseValue()
|
||||
}
|
||||
|
||||
// Send will serialize the first argument, send it to the node and returns the response.
|
||||
func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) {
|
||||
// Ensure that we've got a batch request (array) or a single request (object)
|
||||
arg := call.Argument(0).Object()
|
||||
if arg == nil || (arg.Class() != "Array" && arg.Class() != "Object") {
|
||||
throwJSException("request must be an object or array")
|
||||
}
|
||||
// Convert the otto VM arguments to Go values
|
||||
data, err := call.Otto.Call("JSON.stringify", nil, arg)
|
||||
if err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
reqjson, err := data.ToString()
|
||||
if err != nil {
|
||||
throwJSException(err.Error())
|
||||
}
|
||||
|
||||
var (
|
||||
reqs []rpc.JSONRequest
|
||||
batch = true
|
||||
)
|
||||
if err = json.Unmarshal([]byte(reqjson), &reqs); err != nil {
|
||||
// single request?
|
||||
reqs = make([]rpc.JSONRequest, 1)
|
||||
if err = json.Unmarshal([]byte(reqjson), &reqs[0]); err != nil {
|
||||
throwJSException("invalid request")
|
||||
}
|
||||
batch = false
|
||||
}
|
||||
// Iteratively execute the requests
|
||||
call.Otto.Set("response_len", len(reqs))
|
||||
call.Otto.Run("var ret_response = new Array(response_len);")
|
||||
|
||||
for i, req := range reqs {
|
||||
// Execute the RPC request and parse the reply
|
||||
if err = b.client.Send(&req); err != nil {
|
||||
return newErrorResponse(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
result := make(map[string]interface{})
|
||||
if err = b.client.Recv(&result); err != nil {
|
||||
return newErrorResponse(call, -32603, err.Error(), req.Id)
|
||||
}
|
||||
// Feed the reply back into the JavaScript runtime environment
|
||||
id, _ := result["id"]
|
||||
jsonver, _ := result["jsonrpc"]
|
||||
|
||||
call.Otto.Set("ret_id", id)
|
||||
call.Otto.Set("ret_jsonrpc", jsonver)
|
||||
call.Otto.Set("response_idx", i)
|
||||
|
||||
if res, ok := result["result"]; ok {
|
||||
payload, _ := json.Marshal(res)
|
||||
call.Otto.Set("ret_result", string(payload))
|
||||
response, err = call.Otto.Run(`
|
||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
|
||||
`)
|
||||
continue
|
||||
}
|
||||
if res, ok := result["error"]; ok {
|
||||
payload, _ := json.Marshal(res)
|
||||
call.Otto.Set("ret_result", string(payload))
|
||||
response, err = call.Otto.Run(`
|
||||
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, error: JSON.parse(ret_result) };
|
||||
`)
|
||||
continue
|
||||
}
|
||||
return newErrorResponse(call, -32603, fmt.Sprintf("Invalid response"), new(int64))
|
||||
}
|
||||
// Convert single requests back from batch ones
|
||||
if !batch {
|
||||
call.Otto.Run("ret_response = ret_response[0];")
|
||||
}
|
||||
// Execute any registered callbacks
|
||||
if call.Argument(1).IsObject() {
|
||||
call.Otto.Set("callback", call.Argument(1))
|
||||
call.Otto.Run(`
|
||||
if (Object.prototype.toString.call(callback) == '[object Function]') {
|
||||
callback(null, ret_response);
|
||||
}
|
||||
`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// throwJSException panics on an otto.Value. The Otto VM will recover from the
|
||||
// Go panic and throw msg as a JavaScript error.
|
||||
func throwJSException(msg interface{}) otto.Value {
|
||||
val, err := otto.ToValue(msg)
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to serialize JavaScript exception %v: %v", msg, err)
|
||||
}
|
||||
panic(val)
|
||||
}
|
||||
|
||||
// newErrorResponse creates a JSON RPC error response for a specific request id,
|
||||
// containing the specified error code and error message. Beside returning the
|
||||
// error to the caller, it also sets the ret_error and ret_response JavaScript
|
||||
// variables.
|
||||
func newErrorResponse(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) {
|
||||
// Bundle the error into a JSON RPC call response
|
||||
res := rpc.JSONErrResponse{
|
||||
Version: rpc.JSONRPCVersion,
|
||||
Id: id,
|
||||
Error: rpc.JSONError{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
// Serialize the error response into JavaScript variables
|
||||
errObj, err := json.Marshal(res.Error)
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to serialize JSON RPC error: %v", err)
|
||||
}
|
||||
resObj, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to serialize JSON RPC error response: %v", err)
|
||||
}
|
||||
|
||||
if _, err = call.Otto.Run("ret_error = " + string(errObj)); err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to set `ret_error` to the occurred error: %v", err)
|
||||
}
|
||||
resVal, err := call.Otto.Run("ret_response = " + string(resObj))
|
||||
if err != nil {
|
||||
glog.V(logger.Error).Infof("Failed to set `ret_response` to the JSON RPC response: %v", err)
|
||||
}
|
||||
return resVal
|
||||
}
|
415
console/console.go
Normal file
415
console/console.go
Normal file
@ -0,0 +1,415 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/internal/jsre"
|
||||
"github.com/ethereum/go-ethereum/internal/web3ext"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/peterh/liner"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
var (
|
||||
passwordRegexp = regexp.MustCompile("personal.[nus]")
|
||||
onlyWhitespace = regexp.MustCompile("^\\s*$")
|
||||
exit = regexp.MustCompile("^\\s*exit\\s*;*\\s*$")
|
||||
)
|
||||
|
||||
// HistoryFile is the file within the data directory to store input scrollback.
|
||||
const HistoryFile = "history"
|
||||
|
||||
// DefaultPrompt is the default prompt line prefix to use for user input querying.
|
||||
const DefaultPrompt = "> "
|
||||
|
||||
// Config is te collection of configurations to fine tune the behavior of the
|
||||
// JavaScript console.
|
||||
type Config struct {
|
||||
DataDir string // Data directory to store the console history at
|
||||
DocRoot string // Filesystem path from where to load JavaScript files from
|
||||
Client rpc.Client // RPC client to execute Ethereum requests through
|
||||
Prompt string // Input prompt prefix string (defaults to DefaultPrompt)
|
||||
Prompter UserPrompter // Input prompter to allow interactive user feedback (defaults to TerminalPrompter)
|
||||
Printer io.Writer // Output writer to serialize any display strings to (defaults to os.Stdout)
|
||||
Preload []string // Absolute paths to JavaScript files to preload
|
||||
}
|
||||
|
||||
// Console is a JavaScript interpreted runtime environment. It is a fully fleged
|
||||
// JavaScript console attached to a running node via an external or in-process RPC
|
||||
// client.
|
||||
type Console struct {
|
||||
client rpc.Client // RPC client to execute Ethereum requests through
|
||||
jsre *jsre.JSRE // JavaScript runtime environment running the interpreter
|
||||
prompt string // Input prompt prefix string
|
||||
prompter UserPrompter // Input prompter to allow interactive user feedback
|
||||
histPath string // Absolute path to the console scrollback history
|
||||
history []string // Scroll history maintained by the console
|
||||
printer io.Writer // Output writer to serialize any display strings to
|
||||
}
|
||||
|
||||
func New(config Config) (*Console, error) {
|
||||
// Handle unset config values gracefully
|
||||
if config.Prompter == nil {
|
||||
config.Prompter = Stdin
|
||||
}
|
||||
if config.Prompt == "" {
|
||||
config.Prompt = DefaultPrompt
|
||||
}
|
||||
if config.Printer == nil {
|
||||
config.Printer = colorable.NewColorableStdout()
|
||||
}
|
||||
// Initialize the console and return
|
||||
console := &Console{
|
||||
client: config.Client,
|
||||
jsre: jsre.New(config.DocRoot, config.Printer),
|
||||
prompt: config.Prompt,
|
||||
prompter: config.Prompter,
|
||||
printer: config.Printer,
|
||||
histPath: filepath.Join(config.DataDir, HistoryFile),
|
||||
}
|
||||
if err := console.init(config.Preload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return console, nil
|
||||
}
|
||||
|
||||
// init retrieves the available APIs from the remote RPC provider and initializes
|
||||
// the console's JavaScript namespaces based on the exposed modules.
|
||||
func (c *Console) init(preload []string) error {
|
||||
// Initialize the JavaScript <-> Go RPC bridge
|
||||
bridge := newBridge(c.client, c.prompter, c.printer)
|
||||
c.jsre.Set("jeth", struct{}{})
|
||||
|
||||
jethObj, _ := c.jsre.Get("jeth")
|
||||
jethObj.Object().Set("send", bridge.Send)
|
||||
jethObj.Object().Set("sendAsync", bridge.Send)
|
||||
|
||||
consoleObj, _ := c.jsre.Get("console")
|
||||
consoleObj.Object().Set("log", c.consoleOutput)
|
||||
consoleObj.Object().Set("error", c.consoleOutput)
|
||||
|
||||
// Load all the internal utility JavaScript libraries
|
||||
if err := c.jsre.Compile("bignumber.js", jsre.BigNumber_JS); err != nil {
|
||||
return fmt.Errorf("bignumber.js: %v", err)
|
||||
}
|
||||
if err := c.jsre.Compile("web3.js", jsre.Web3_JS); err != nil {
|
||||
return fmt.Errorf("web3.js: %v", err)
|
||||
}
|
||||
if _, err := c.jsre.Run("var Web3 = require('web3');"); err != nil {
|
||||
return fmt.Errorf("web3 require: %v", err)
|
||||
}
|
||||
if _, err := c.jsre.Run("var web3 = new Web3(jeth);"); err != nil {
|
||||
return fmt.Errorf("web3 provider: %v", err)
|
||||
}
|
||||
// Load the supported APIs into the JavaScript runtime environment
|
||||
apis, err := c.client.SupportedModules()
|
||||
if err != nil {
|
||||
return fmt.Errorf("api modules: %v", err)
|
||||
}
|
||||
flatten := "var eth = web3.eth; var personal = web3.personal; "
|
||||
for api := range apis {
|
||||
if api == "web3" {
|
||||
continue // manually mapped or ignore
|
||||
}
|
||||
if file, ok := web3ext.Modules[api]; ok {
|
||||
if err = c.jsre.Compile(fmt.Sprintf("%s.js", api), file); err != nil {
|
||||
return fmt.Errorf("%s.js: %v", api, err)
|
||||
}
|
||||
flatten += fmt.Sprintf("var %s = web3.%s; ", api, api)
|
||||
}
|
||||
}
|
||||
if _, err = c.jsre.Run(flatten); err != nil {
|
||||
return fmt.Errorf("namespace flattening: %v", err)
|
||||
}
|
||||
// Initialize the global name register (disabled for now)
|
||||
//c.jsre.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
|
||||
|
||||
// If the console is in interactive mode, instrument password related methods to query the user
|
||||
if c.prompter != nil {
|
||||
// Retrieve the account management object to instrument
|
||||
personal, err := c.jsre.Get("personal")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Override the unlockAccount and newAccount methods since these require user interaction.
|
||||
// Assign the jeth.unlockAccount and jeth.newAccount in the Console the original web3 callbacks.
|
||||
// These will be called by the jeth.* methods after they got the password from the user and send
|
||||
// the original web3 request to the backend.
|
||||
if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface
|
||||
if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
|
||||
return fmt.Errorf("personal.unlockAccount: %v", err)
|
||||
}
|
||||
if _, err = c.jsre.Run(`jeth.newAccount = personal.newAccount;`); err != nil {
|
||||
return fmt.Errorf("personal.newAccount: %v", err)
|
||||
}
|
||||
obj.Set("unlockAccount", bridge.UnlockAccount)
|
||||
obj.Set("newAccount", bridge.NewAccount)
|
||||
}
|
||||
}
|
||||
// The admin.sleep and admin.sleepBlocks are offered by the console and not by the RPC layer.
|
||||
admin, err := c.jsre.Get("admin")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if obj := admin.Object(); obj != nil { // make sure the admin api is enabled over the interface
|
||||
obj.Set("sleepBlocks", bridge.SleepBlocks)
|
||||
obj.Set("sleep", bridge.Sleep)
|
||||
}
|
||||
// Preload any JavaScript files before starting the console
|
||||
for _, path := range preload {
|
||||
if err := c.jsre.Exec(path); err != nil {
|
||||
failure := err.Error()
|
||||
if ottoErr, ok := err.(*otto.Error); ok {
|
||||
failure = ottoErr.String()
|
||||
}
|
||||
return fmt.Errorf("%s: %v", path, failure)
|
||||
}
|
||||
}
|
||||
// Configure the console's input prompter for scrollback and tab completion
|
||||
if c.prompter != nil {
|
||||
if content, err := ioutil.ReadFile(c.histPath); err != nil {
|
||||
c.prompter.SetHistory(nil)
|
||||
} else {
|
||||
c.history = strings.Split(string(content), "\n")
|
||||
c.prompter.SetHistory(c.history)
|
||||
}
|
||||
c.prompter.SetWordCompleter(c.AutoCompleteInput)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// consoleOutput is an override for the console.log and console.error methods to
|
||||
// stream the output into the configured output stream instead of stdout.
|
||||
func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
|
||||
output := []string{}
|
||||
for _, argument := range call.ArgumentList {
|
||||
output = append(output, fmt.Sprintf("%v", argument))
|
||||
}
|
||||
fmt.Fprintln(c.printer, strings.Join(output, " "))
|
||||
return otto.Value{}
|
||||
}
|
||||
|
||||
// AutoCompleteInput is a pre-assembled word completer to be used by the user
|
||||
// input prompter to provide hints to the user about the methods available.
|
||||
func (c *Console) AutoCompleteInput(line string, pos int) (string, []string, string) {
|
||||
// No completions can be provided for empty inputs
|
||||
if len(line) == 0 || pos == 0 {
|
||||
return "", nil, ""
|
||||
}
|
||||
// Chunck data to relevant part for autocompletion
|
||||
// E.g. in case of nested lines eth.getBalance(eth.coinb<tab><tab>
|
||||
start := 0
|
||||
for start = pos - 1; start > 0; start-- {
|
||||
// Skip all methods and namespaces (i.e. including te dot)
|
||||
if line[start] == '.' || (line[start] >= 'a' && line[start] <= 'z') || (line[start] >= 'A' && line[start] <= 'Z') {
|
||||
continue
|
||||
}
|
||||
// Handle web3 in a special way (i.e. other numbers aren't auto completed)
|
||||
if start >= 3 && line[start-3:start] == "web3" {
|
||||
start -= 3
|
||||
continue
|
||||
}
|
||||
// We've hit an unexpected character, autocomplete form here
|
||||
start++
|
||||
break
|
||||
}
|
||||
return line[:start], c.jsre.CompleteKeywords(line[start:pos]), line[pos:]
|
||||
}
|
||||
|
||||
// Welcome show summary of current Geth instance and some metadata about the
|
||||
// console's available modules.
|
||||
func (c *Console) Welcome() {
|
||||
// Print some generic Geth metadata
|
||||
fmt.Fprintf(c.printer, "Welcome to the Geth JavaScript console!\n\n")
|
||||
c.jsre.Run(`
|
||||
console.log("instance: " + web3.version.node);
|
||||
console.log("coinbase: " + eth.coinbase);
|
||||
console.log("at block: " + eth.blockNumber + " (" + new Date(1000 * eth.getBlock(eth.blockNumber).timestamp) + ")");
|
||||
console.log(" datadir: " + admin.datadir);
|
||||
`)
|
||||
// List all the supported modules for the user to call
|
||||
if apis, err := c.client.SupportedModules(); err == nil {
|
||||
modules := make([]string, 0, len(apis))
|
||||
for api, version := range apis {
|
||||
modules = append(modules, fmt.Sprintf("%s:%s", api, version))
|
||||
}
|
||||
sort.Strings(modules)
|
||||
fmt.Fprintln(c.printer, " modules:", strings.Join(modules, " "))
|
||||
}
|
||||
fmt.Fprintln(c.printer)
|
||||
}
|
||||
|
||||
// Evaluate executes code and pretty prints the result to the specified output
|
||||
// stream.
|
||||
func (c *Console) Evaluate(statement string) error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(c.printer, "[native] error: %v\n", r)
|
||||
}
|
||||
}()
|
||||
if err := c.jsre.Evaluate(statement, c.printer); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interactive starts an interactive user session, where input is propted from
|
||||
// the configured user prompter.
|
||||
func (c *Console) Interactive() {
|
||||
var (
|
||||
prompt = c.prompt // Current prompt line (used for multi-line inputs)
|
||||
indents = 0 // Current number of input indents (used for multi-line inputs)
|
||||
input = "" // Current user input
|
||||
scheduler = make(chan string) // Channel to send the next prompt on and receive the input
|
||||
)
|
||||
// Start a goroutine to listen for promt requests and send back inputs
|
||||
go func() {
|
||||
for {
|
||||
// Read the next user input
|
||||
line, err := c.prompter.PromptInput(<-scheduler)
|
||||
if err != nil {
|
||||
// In case of an error, either clear the prompt or fail
|
||||
if err == liner.ErrPromptAborted { // ctrl-C
|
||||
prompt, indents, input = c.prompt, 0, ""
|
||||
scheduler <- ""
|
||||
continue
|
||||
}
|
||||
close(scheduler)
|
||||
return
|
||||
}
|
||||
// User input retrieved, send for interpretation and loop
|
||||
scheduler <- line
|
||||
}
|
||||
}()
|
||||
// Monitor Ctrl-C too in case the input is empty and we need to bail
|
||||
abort := make(chan os.Signal, 1)
|
||||
signal.Notify(abort, os.Interrupt)
|
||||
|
||||
// Start sending prompts to the user and reading back inputs
|
||||
for {
|
||||
// Send the next prompt, triggering an input read and process the result
|
||||
scheduler <- prompt
|
||||
select {
|
||||
case <-abort:
|
||||
// User forcefully quite the console
|
||||
fmt.Fprintln(c.printer, "caught interrupt, exiting")
|
||||
return
|
||||
|
||||
case line, ok := <-scheduler:
|
||||
// User input was returned by the prompter, handle special cases
|
||||
if !ok || (indents <= 0 && exit.MatchString(line)) {
|
||||
return
|
||||
}
|
||||
if onlyWhitespace.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
// Append the line to the input and check for multi-line interpretation
|
||||
input += line + "\n"
|
||||
|
||||
indents = countIndents(input)
|
||||
if indents <= 0 {
|
||||
prompt = c.prompt
|
||||
} else {
|
||||
prompt = strings.Repeat(".", indents*3) + " "
|
||||
}
|
||||
// If all the needed lines are present, save the command and run
|
||||
if indents <= 0 {
|
||||
if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) {
|
||||
if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] {
|
||||
c.history = append(c.history, command)
|
||||
if c.prompter != nil {
|
||||
c.prompter.AppendHistory(command)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Evaluate(input)
|
||||
input = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// countIndents returns the number of identations for the given input.
|
||||
// In case of invalid input such as var a = } the result can be negative.
|
||||
func countIndents(input string) int {
|
||||
var (
|
||||
indents = 0
|
||||
inString = false
|
||||
strOpenChar = ' ' // keep track of the string open char to allow var str = "I'm ....";
|
||||
charEscaped = false // keep track if the previous char was the '\' char, allow var str = "abc\"def";
|
||||
)
|
||||
|
||||
for _, c := range input {
|
||||
switch c {
|
||||
case '\\':
|
||||
// indicate next char as escaped when in string and previous char isn't escaping this backslash
|
||||
if !charEscaped && inString {
|
||||
charEscaped = true
|
||||
}
|
||||
case '\'', '"':
|
||||
if inString && !charEscaped && strOpenChar == c { // end string
|
||||
inString = false
|
||||
} else if !inString && !charEscaped { // begin string
|
||||
inString = true
|
||||
strOpenChar = c
|
||||
}
|
||||
charEscaped = false
|
||||
case '{', '(':
|
||||
if !inString { // ignore brackets when in string, allow var str = "a{"; without indenting
|
||||
indents++
|
||||
}
|
||||
charEscaped = false
|
||||
case '}', ')':
|
||||
if !inString {
|
||||
indents--
|
||||
}
|
||||
charEscaped = false
|
||||
default:
|
||||
charEscaped = false
|
||||
}
|
||||
}
|
||||
|
||||
return indents
|
||||
}
|
||||
|
||||
// Execute runs the JavaScript file specified as the argument.
|
||||
func (c *Console) Execute(path string) error {
|
||||
return c.jsre.Exec(path)
|
||||
}
|
||||
|
||||
// Stop cleans up the console and terminates the runtime envorinment.
|
||||
func (c *Console) Stop(graceful bool) error {
|
||||
if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(c.histPath, 0600); err != nil { // Force 0600, even if it was different previously
|
||||
return err
|
||||
}
|
||||
c.jsre.Stop(graceful)
|
||||
return nil
|
||||
}
|
342
console/console_test.go
Normal file
342
console/console_test.go
Normal file
@ -0,0 +1,342 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package console
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/internal/jsre"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
)
|
||||
|
||||
const (
|
||||
testInstance = "console-tester"
|
||||
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
|
||||
)
|
||||
|
||||
// hookedPrompter implements UserPrompter to simulate use input via channels.
|
||||
type hookedPrompter struct {
|
||||
scheduler chan string
|
||||
}
|
||||
|
||||
func (p *hookedPrompter) PromptInput(prompt string) (string, error) {
|
||||
// Send the prompt to the tester
|
||||
select {
|
||||
case p.scheduler <- prompt:
|
||||
case <-time.After(time.Second):
|
||||
return "", errors.New("prompt timeout")
|
||||
}
|
||||
// Retrieve the response and feed to the console
|
||||
select {
|
||||
case input := <-p.scheduler:
|
||||
return input, nil
|
||||
case <-time.After(time.Second):
|
||||
return "", errors.New("input timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hookedPrompter) PromptPassword(prompt string) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) {
|
||||
return false, errors.New("not implemented")
|
||||
}
|
||||
func (p *hookedPrompter) SetHistory(history []string) {}
|
||||
func (p *hookedPrompter) AppendHistory(command string) {}
|
||||
func (p *hookedPrompter) SetWordCompleter(completer WordCompleter) {}
|
||||
|
||||
// tester is a console test environment for the console tests to operate on.
|
||||
type tester struct {
|
||||
workspace string
|
||||
stack *node.Node
|
||||
ethereum *eth.Ethereum
|
||||
console *Console
|
||||
input *hookedPrompter
|
||||
output *bytes.Buffer
|
||||
|
||||
lastConfirm string
|
||||
}
|
||||
|
||||
// newTester creates a test environment based on which the console can operate.
|
||||
// Please ensure you call Close() on the returned tester to avoid leaks.
|
||||
func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
|
||||
// Create a temporary storage for the node keys and initialize it
|
||||
workspace, err := ioutil.TempDir("", "console-tester-")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temporary keystore: %v", err)
|
||||
}
|
||||
accman := accounts.NewPlaintextManager(filepath.Join(workspace, "keystore"))
|
||||
|
||||
// Create a networkless protocol stack and start an Ethereum service within
|
||||
stack, err := node.New(&node.Config{DataDir: workspace, Name: testInstance, NoDiscovery: true})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create node: %v", err)
|
||||
}
|
||||
ethConf := ð.Config{
|
||||
ChainConfig: &core.ChainConfig{HomesteadBlock: new(big.Int)},
|
||||
Etherbase: common.HexToAddress(testAddress),
|
||||
AccountManager: accman,
|
||||
PowTest: true,
|
||||
}
|
||||
if confOverride != nil {
|
||||
confOverride(ethConf)
|
||||
}
|
||||
if err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
|
||||
t.Fatalf("failed to register Ethereum protocol: %v", err)
|
||||
}
|
||||
// Start the node and assemble the JavaScript console around it
|
||||
if err = stack.Start(); err != nil {
|
||||
t.Fatalf("failed to start test stack: %v", err)
|
||||
}
|
||||
client, err := stack.Attach()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to attach to node: %v", err)
|
||||
}
|
||||
prompter := &hookedPrompter{scheduler: make(chan string)}
|
||||
printer := new(bytes.Buffer)
|
||||
|
||||
console, err := New(Config{
|
||||
DataDir: stack.DataDir(),
|
||||
DocRoot: "testdata",
|
||||
Client: client,
|
||||
Prompter: prompter,
|
||||
Printer: printer,
|
||||
Preload: []string{"preload.js"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create JavaScript console: %v", err)
|
||||
}
|
||||
// Create the final tester and return
|
||||
var ethereum *eth.Ethereum
|
||||
stack.Service(ðereum)
|
||||
|
||||
return &tester{
|
||||
workspace: workspace,
|
||||
stack: stack,
|
||||
ethereum: ethereum,
|
||||
console: console,
|
||||
input: prompter,
|
||||
output: printer,
|
||||
}
|
||||
}
|
||||
|
||||
// Close cleans up any temporary data folders and held resources.
|
||||
func (env *tester) Close(t *testing.T) {
|
||||
if err := env.console.Stop(false); err != nil {
|
||||
t.Errorf("failed to stop embedded console: %v", err)
|
||||
}
|
||||
if err := env.stack.Stop(); err != nil {
|
||||
t.Errorf("failed to stop embedded node: %v", err)
|
||||
}
|
||||
os.RemoveAll(env.workspace)
|
||||
}
|
||||
|
||||
// Tests that the node lists the correct welcome message, notably that it contains
|
||||
// the instance name, coinbase account, block number, data directory and supported
|
||||
// console modules.
|
||||
func TestWelcome(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
||||
tester.console.Welcome()
|
||||
|
||||
output := string(tester.output.Bytes())
|
||||
if want := "Welcome"; !strings.Contains(output, want) {
|
||||
t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want)
|
||||
}
|
||||
if want := fmt.Sprintf("instance: %s", testInstance); !strings.Contains(output, want) {
|
||||
t.Fatalf("console output missing instance: have\n%s\nwant also %s", output, want)
|
||||
}
|
||||
if want := fmt.Sprintf("coinbase: %s", testAddress); !strings.Contains(output, want) {
|
||||
t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want)
|
||||
}
|
||||
if want := "at block: 0"; !strings.Contains(output, want) {
|
||||
t.Fatalf("console output missing sync status: have\n%s\nwant also %s", output, want)
|
||||
}
|
||||
if want := fmt.Sprintf("datadir: %s", tester.workspace); !strings.Contains(output, want) {
|
||||
t.Fatalf("console output missing coinbase: have\n%s\nwant also %s", output, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that JavaScript statement evaluation works as intended.
|
||||
func TestEvaluate(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
||||
tester.console.Evaluate("2 + 2")
|
||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "4") {
|
||||
t.Fatalf("statement evaluation failed: have %s, want %s", output, "4")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the console can be used in interactive mode.
|
||||
func TestInteractive(t *testing.T) {
|
||||
// Create a tester and run an interactive console in the background
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
||||
go tester.console.Interactive()
|
||||
|
||||
// Wait for a promt and send a statement back
|
||||
select {
|
||||
case <-tester.input.scheduler:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("initial prompt timeout")
|
||||
}
|
||||
select {
|
||||
case tester.input.scheduler <- "2+2":
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("input feedback timeout")
|
||||
}
|
||||
// Wait for the second promt and ensure first statement was evaluated
|
||||
select {
|
||||
case <-tester.input.scheduler:
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("secondary prompt timeout")
|
||||
}
|
||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "4") {
|
||||
t.Fatalf("statement evaluation failed: have %s, want %s", output, "4")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that preloaded JavaScript files have been executed before user is given
|
||||
// input.
|
||||
func TestPreload(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
||||
tester.console.Evaluate("preloaded")
|
||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "some-preloaded-string") {
|
||||
t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that JavaScript scripts can be executes from the configured asset path.
|
||||
func TestExecute(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
||||
tester.console.Execute("exec.js")
|
||||
|
||||
tester.console.Evaluate("execed")
|
||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "some-executed-string") {
|
||||
t.Fatalf("execed variable missing: have %s, want %s", output, "some-executed-string")
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the JavaScript objects returned by statement executions are properly
|
||||
// pretty printed instead of just displaing "[object]".
|
||||
func TestPrettyPrint(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
||||
tester.console.Evaluate("obj = {int: 1, string: 'two', list: [3, 3, 3], obj: {null: null, func: function(){}}}")
|
||||
|
||||
// Define some specially formatted fields
|
||||
var (
|
||||
one = jsre.NumberColor("1")
|
||||
two = jsre.StringColor("\"two\"")
|
||||
three = jsre.NumberColor("3")
|
||||
null = jsre.SpecialColor("null")
|
||||
fun = jsre.FunctionColor("function()")
|
||||
)
|
||||
// Assemble the actual output we're after and verify
|
||||
want := `{
|
||||
int: ` + one + `,
|
||||
list: [` + three + `, ` + three + `, ` + three + `],
|
||||
obj: {
|
||||
null: ` + null + `,
|
||||
func: ` + fun + `
|
||||
},
|
||||
string: ` + two + `
|
||||
}
|
||||
`
|
||||
if output := string(tester.output.Bytes()); output != want {
|
||||
t.Fatalf("pretty print mismatch: have %s, want %s", output, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that the JavaScript exceptions are properly formatted and colored.
|
||||
func TestPrettyError(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
tester.console.Evaluate("throw 'hello'")
|
||||
|
||||
want := jsre.ErrorColor("hello") + "\n"
|
||||
if output := string(tester.output.Bytes()); output != want {
|
||||
t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that tests if the number of indents for JS input is calculated correct.
|
||||
func TestIndenting(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expectedIndentCount int
|
||||
}{
|
||||
{`var a = 1;`, 0},
|
||||
{`"some string"`, 0},
|
||||
{`"some string with (parentesis`, 0},
|
||||
{`"some string with newline
|
||||
("`, 0},
|
||||
{`function v(a,b) {}`, 0},
|
||||
{`function f(a,b) { var str = "asd("; };`, 0},
|
||||
{`function f(a) {`, 1},
|
||||
{`function f(a, function(b) {`, 2},
|
||||
{`function f(a, function(b) {
|
||||
var str = "a)}";
|
||||
});`, 0},
|
||||
{`function f(a,b) {
|
||||
var str = "a{b(" + a, ", " + b;
|
||||
}`, 0},
|
||||
{`var str = "\"{"`, 0},
|
||||
{`var str = "'("`, 0},
|
||||
{`var str = "\\{"`, 0},
|
||||
{`var str = "\\\\{"`, 0},
|
||||
{`var str = 'a"{`, 0},
|
||||
{`var obj = {`, 1},
|
||||
{`var obj = { {a:1`, 2},
|
||||
{`var obj = { {a:1}`, 1},
|
||||
{`var obj = { {a:1}, b:2}`, 0},
|
||||
{`var obj = {}`, 0},
|
||||
{`var obj = {
|
||||
a: 1, b: 2
|
||||
}`, 0},
|
||||
{`var test = }`, -1},
|
||||
{`var str = "a\""; var obj = {`, 1},
|
||||
}
|
||||
|
||||
for i, tt := range testCases {
|
||||
counted := countIndents(tt.input)
|
||||
if counted != tt.expectedIndentCount {
|
||||
t.Errorf("test %d: invalid indenting: have %d, want %d", i, counted, tt.expectedIndentCount)
|
||||
}
|
||||
}
|
||||
}
|
165
console/prompter.go
Normal file
165
console/prompter.go
Normal file
@ -0,0 +1,165 @@
|
||||
// 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 console
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/peterh/liner"
|
||||
)
|
||||
|
||||
// Stdin holds the stdin line reader (also using stdout for printing prompts).
|
||||
// Only this reader may be used for input because it keeps an internal buffer.
|
||||
var Stdin = newTerminalPrompter()
|
||||
|
||||
// UserPrompter defines the methods needed by the console to promt the user for
|
||||
// various types of inputs.
|
||||
type UserPrompter interface {
|
||||
// PromptInput displays the given prompt to the user and requests some textual
|
||||
// data to be entered, returning the input of the user.
|
||||
PromptInput(prompt string) (string, error)
|
||||
|
||||
// PromptPassword displays the given prompt to the user and requests some textual
|
||||
// data to be entered, but one which must not be echoed out into the terminal.
|
||||
// The method returns the input provided by the user.
|
||||
PromptPassword(prompt string) (string, error)
|
||||
|
||||
// PromptConfirm displays the given prompt to the user and requests a boolean
|
||||
// choice to be made, returning that choice.
|
||||
PromptConfirm(prompt string) (bool, error)
|
||||
|
||||
// SetHistory sets the the input scrollback history that the prompter will allow
|
||||
// the user to scoll back to.
|
||||
SetHistory(history []string)
|
||||
|
||||
// AppendHistory appends an entry to the scrollback history. It should be called
|
||||
// if and only if the prompt to append was a valid command.
|
||||
AppendHistory(command string)
|
||||
|
||||
// SetWordCompleter sets the completion function that the prompter will call to
|
||||
// fetch completion candidates when the user presses tab.
|
||||
SetWordCompleter(completer WordCompleter)
|
||||
}
|
||||
|
||||
// WordCompleter takes the currently edited line with the cursor position and
|
||||
// returns the completion candidates for the partial word to be completed. If
|
||||
// the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello,
|
||||
// wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world",
|
||||
// "Word"}, "!!!") to have "Hello, world!!!".
|
||||
type WordCompleter func(line string, pos int) (string, []string, string)
|
||||
|
||||
// terminalPrompter is a UserPrompter backed by the liner package. It supports
|
||||
// prompting the user for various input, among others for non-echoing password
|
||||
// input.
|
||||
type terminalPrompter struct {
|
||||
*liner.State
|
||||
warned bool
|
||||
supported bool
|
||||
normalMode liner.ModeApplier
|
||||
rawMode liner.ModeApplier
|
||||
}
|
||||
|
||||
// newTerminalPrompter creates a liner based user input prompter working off the
|
||||
// standard input and output streams.
|
||||
func newTerminalPrompter() *terminalPrompter {
|
||||
p := new(terminalPrompter)
|
||||
// Get the original mode before calling NewLiner.
|
||||
// This is usually regular "cooked" mode where characters echo.
|
||||
normalMode, _ := liner.TerminalMode()
|
||||
// Turn on liner. It switches to raw mode.
|
||||
p.State = liner.NewLiner()
|
||||
rawMode, err := liner.TerminalMode()
|
||||
if err != nil || !liner.TerminalSupported() {
|
||||
p.supported = false
|
||||
} else {
|
||||
p.supported = true
|
||||
p.normalMode = normalMode
|
||||
p.rawMode = rawMode
|
||||
// Switch back to normal mode while we're not prompting.
|
||||
normalMode.ApplyMode()
|
||||
}
|
||||
p.SetCtrlCAborts(true)
|
||||
p.SetTabCompletionStyle(liner.TabPrints)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// PromptInput displays the given prompt to the user and requests some textual
|
||||
// data to be entered, returning the input of the user.
|
||||
func (p *terminalPrompter) PromptInput(prompt string) (string, error) {
|
||||
if p.supported {
|
||||
p.rawMode.ApplyMode()
|
||||
defer p.normalMode.ApplyMode()
|
||||
} else {
|
||||
// liner tries to be smart about printing the prompt
|
||||
// and doesn't print anything if input is redirected.
|
||||
// Un-smart it by printing the prompt always.
|
||||
fmt.Print(prompt)
|
||||
prompt = ""
|
||||
defer fmt.Println()
|
||||
}
|
||||
return p.State.Prompt(prompt)
|
||||
}
|
||||
|
||||
// PromptPassword displays the given prompt to the user and requests some textual
|
||||
// data to be entered, but one which must not be echoed out into the terminal.
|
||||
// The method returns the input provided by the user.
|
||||
func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) {
|
||||
if p.supported {
|
||||
p.rawMode.ApplyMode()
|
||||
defer p.normalMode.ApplyMode()
|
||||
return p.State.PasswordPrompt(prompt)
|
||||
}
|
||||
if !p.warned {
|
||||
fmt.Println("!! Unsupported terminal, password will be echoed.")
|
||||
p.warned = true
|
||||
}
|
||||
// Just as in Prompt, handle printing the prompt here instead of relying on liner.
|
||||
fmt.Print(prompt)
|
||||
passwd, err = p.State.Prompt("")
|
||||
fmt.Println()
|
||||
return passwd, err
|
||||
}
|
||||
|
||||
// PromptConfirm displays the given prompt to the user and requests a boolean
|
||||
// choice to be made, returning that choice.
|
||||
func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) {
|
||||
input, err := p.Prompt(prompt + " [y/N] ")
|
||||
if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// SetHistory sets the the input scrollback history that the prompter will allow
|
||||
// the user to scoll back to.
|
||||
func (p *terminalPrompter) SetHistory(history []string) {
|
||||
p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n")))
|
||||
}
|
||||
|
||||
// AppendHistory appends an entry to the scrollback history. It should be called
|
||||
// if and only if the prompt to append was a valid command.
|
||||
func (p *terminalPrompter) AppendHistory(command string) {
|
||||
p.State.AppendHistory(command)
|
||||
}
|
||||
|
||||
// SetWordCompleter sets the completion function that the prompter will call to
|
||||
// fetch completion candidates when the user presses tab.
|
||||
func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) {
|
||||
p.State.SetWordCompleter(liner.WordCompleter(completer))
|
||||
}
|
1
console/testdata/exec.js
vendored
Normal file
1
console/testdata/exec.js
vendored
Normal file
@ -0,0 +1 @@
|
||||
var execed = "some-executed-string";
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user