mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
Merge with ethereum
This commit is contained in:
commit
85a6fead74
986 changed files with 140754 additions and 34295 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto
|
||||||
|
*.sol linguist-language=Solidity
|
||||||
|
|
|
||||||
20
.github/CODEOWNERS
vendored
20
.github/CODEOWNERS
vendored
|
|
@ -9,4 +9,24 @@ les/ @zsfelfoldi
|
||||||
light/ @zsfelfoldi
|
light/ @zsfelfoldi
|
||||||
mobile/ @karalabe
|
mobile/ @karalabe
|
||||||
p2p/ @fjl @zsfelfoldi
|
p2p/ @fjl @zsfelfoldi
|
||||||
|
swarm/bmt @zelig
|
||||||
|
swarm/dev @lmars
|
||||||
|
swarm/fuse @jmozah @holisticode
|
||||||
|
swarm/grafana_dashboards @nonsense
|
||||||
|
swarm/metrics @nonsense @holisticode
|
||||||
|
swarm/multihash @nolash
|
||||||
|
swarm/network/bitvector @zelig @janos @gbalint
|
||||||
|
swarm/network/priorityqueue @zelig @janos @gbalint
|
||||||
|
swarm/network/simulations @zelig
|
||||||
|
swarm/network/stream @janos @zelig @gbalint @holisticode @justelad
|
||||||
|
swarm/network/stream/intervals @janos
|
||||||
|
swarm/network/stream/testing @zelig
|
||||||
|
swarm/pot @zelig
|
||||||
|
swarm/pss @nolash @zelig @nonsense
|
||||||
|
swarm/services @zelig
|
||||||
|
swarm/state @justelad
|
||||||
|
swarm/storage/encryption @gbalint @zelig @nagydani
|
||||||
|
swarm/storage/mock @janos
|
||||||
|
swarm/storage/mru @nolash
|
||||||
|
swarm/testutil @lmars
|
||||||
whisper/ @gballet @gluk256
|
whisper/ @gballet @gluk256
|
||||||
|
|
|
||||||
56
.travis.yml
56
.travis.yml
|
|
@ -12,34 +12,33 @@ matrix:
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# These are the latest Go versions.
|
# These are the latest Go versions.
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
go: "1.10"
|
go: 1.10.x
|
||||||
script:
|
script:
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: "1.10"
|
go: 1.10.x
|
||||||
script:
|
script:
|
||||||
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
||||||
- brew update
|
- brew update
|
||||||
- brew install caskroom/cask/brew-cask
|
|
||||||
- brew cask install osxfuse
|
- brew cask install osxfuse
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# This builder only tests code linters on latest version of Go
|
# This builder only tests code linters on latest version of Go
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
go: "1.10"
|
go: 1.10.x
|
||||||
env:
|
env:
|
||||||
- lint
|
- lint
|
||||||
git:
|
git:
|
||||||
|
|
@ -47,14 +46,12 @@ matrix:
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go lint
|
- go run build/ci.go lint
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA and Linux Azure uploads
|
# This builder does the Ubuntu PPA upload
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
go: 1.10.x
|
||||||
go: "1.10"
|
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- azure-linux
|
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
|
|
@ -63,11 +60,25 @@ matrix:
|
||||||
- devscripts
|
- devscripts
|
||||||
- debhelper
|
- debhelper
|
||||||
- dput
|
- dput
|
||||||
- gcc-multilib
|
|
||||||
- fakeroot
|
- fakeroot
|
||||||
script:
|
script:
|
||||||
# Build for the primary platforms that Trusty can manage
|
|
||||||
- go run build/ci.go debsrc -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>" -upload ppa:ethereum/ethereum
|
- go run build/ci.go debsrc -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>" -upload ppa:ethereum/ethereum
|
||||||
|
|
||||||
|
# This builder does the Linux Azure uploads
|
||||||
|
- os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
go: 1.10.x
|
||||||
|
env:
|
||||||
|
- azure-linux
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- gcc-multilib
|
||||||
|
script:
|
||||||
|
# Build for the primary platforms that Trusty can manage
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go install -arch 386
|
- go run build/ci.go install -arch 386
|
||||||
|
|
@ -91,7 +102,7 @@ matrix:
|
||||||
dist: trusty
|
dist: trusty
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: "1.10"
|
go: 1.10.x
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
git:
|
git:
|
||||||
|
|
@ -115,7 +126,7 @@ matrix:
|
||||||
|
|
||||||
# This builder does the Android Maven and Azure uploads
|
# This builder does the Android Maven and Azure uploads
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: precise # Needed for the android tools
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
|
@ -135,16 +146,16 @@ matrix:
|
||||||
git:
|
git:
|
||||||
submodules: false # avoid cloning ethereum/tests
|
submodules: false # avoid cloning ethereum/tests
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz | tar -xz
|
- curl https://storage.googleapis.com/golang/go1.10.3.linux-amd64.tar.gz | tar -xz
|
||||||
- export PATH=`pwd`/go/bin:$PATH
|
- export PATH=`pwd`/go/bin:$PATH
|
||||||
- export GOROOT=`pwd`/go
|
- export GOROOT=`pwd`/go
|
||||||
- export GOPATH=$HOME/go
|
- export GOPATH=$HOME/go
|
||||||
script:
|
script:
|
||||||
# Build the Android archive and upload it to Maven Central and Azure
|
# Build the Android archive and upload it to Maven Central and Azure
|
||||||
- curl https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip -o android-ndk-r15c.zip
|
- curl https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip -o android-ndk-r17b.zip
|
||||||
- unzip -q android-ndk-r15c.zip && rm android-ndk-r15c.zip
|
- unzip -q android-ndk-r17b.zip && rm android-ndk-r17b.zip
|
||||||
- mv android-ndk-r15c $HOME
|
- mv android-ndk-r17b $HOME
|
||||||
- export ANDROID_NDK=$HOME/android-ndk-r15c
|
- export ANDROID_NDK=$HOME/android-ndk-r17b
|
||||||
|
|
||||||
- mkdir -p $GOPATH/src/github.com/ethereum
|
- mkdir -p $GOPATH/src/github.com/ethereum
|
||||||
- ln -s `pwd` $GOPATH/src/github.com/ethereum
|
- ln -s `pwd` $GOPATH/src/github.com/ethereum
|
||||||
|
|
@ -152,7 +163,7 @@ matrix:
|
||||||
|
|
||||||
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
||||||
- os: osx
|
- os: osx
|
||||||
go: "1.10"
|
go: 1.10.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- azure-ios
|
- azure-ios
|
||||||
|
|
@ -181,8 +192,7 @@ matrix:
|
||||||
# This builder does the Azure archive purges to avoid accumulating junk
|
# This builder does the Azure archive purges to avoid accumulating junk
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
go: 1.10.x
|
||||||
go: "1.10"
|
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
git:
|
git:
|
||||||
|
|
|
||||||
1
AUTHORS
1
AUTHORS
|
|
@ -171,3 +171,4 @@ xiekeyang <xiekeyang@users.noreply.github.com>
|
||||||
yoza <yoza.is12s@gmail.com>
|
yoza <yoza.is12s@gmail.com>
|
||||||
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||||
Максим Чусовлянов <mchusovlianov@gmail.com>
|
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||||
|
Ralph Caraveo <deckarep@gmail.com>
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ FROM alpine:latest
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
||||||
|
|
||||||
EXPOSE 8545 8546 30303 30303/udp 30304/udp
|
EXPOSE 8545 8546 30303 30303/udp
|
||||||
ENTRYPOINT ["geth"]
|
ENTRYPOINT ["geth"]
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,4 @@ FROM alpine:latest
|
||||||
RUN apk add --no-cache ca-certificates
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
||||||
|
|
||||||
EXPOSE 8545 8546 30303 30303/udp 30304/udp
|
EXPOSE 8545 8546 30303 30303/udp
|
||||||
|
|
|
||||||
4
Makefile
4
Makefile
|
|
@ -37,7 +37,11 @@ ios:
|
||||||
test: all
|
test: all
|
||||||
build/env.sh go run build/ci.go test
|
build/env.sh go run build/ci.go test
|
||||||
|
|
||||||
|
lint: ## Run linters.
|
||||||
|
build/env.sh go run build/ci.go lint
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
./build/clean_go_build_cache.sh
|
||||||
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
||||||
|
|
||||||
# The devtools target installs tools required for 'go generate'.
|
# The devtools target installs tools required for 'go generate'.
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ The go-ethereum project comes with several wrappers/executables found in the `cm
|
||||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||||
| `swarm` | swarm daemon and tools. This is the entrypoint for the swarm network. `swarm --help` for command line options and subcommands. See https://swarm-guide.readthedocs.io for swarm documentation. |
|
| `swarm` | Swarm daemon and tools. This is the entrypoint for the Swarm network. `swarm --help` for command line options and subcommands. See [Swarm README](https://github.com/ethereum/go-ethereum/tree/master/swarm) for more information. |
|
||||||
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
||||||
|
|
||||||
## Running geth
|
## Running geth
|
||||||
|
|
@ -142,7 +142,7 @@ Do not forget `--rpcaddr 0.0.0.0`, if you want to access RPC from other containe
|
||||||
### Programatically interfacing Geth nodes
|
### Programatically interfacing Geth nodes
|
||||||
|
|
||||||
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
As a developer, sooner rather than later you'll want to start interacting with Geth and the Ethereum
|
||||||
network via your own programs and not manually through the console. To aid this, Geth has built in
|
network via your own programs and not manually through the console. To aid this, Geth has built-in
|
||||||
support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) and
|
support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC) and
|
||||||
[Geth specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). These can be
|
[Geth specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)). These can be
|
||||||
exposed via HTTP, WebSockets and IPC (unix sockets on unix based platforms, and named pipes on Windows).
|
exposed via HTTP, WebSockets and IPC (unix sockets on unix based platforms, and named pipes on Windows).
|
||||||
|
|
|
||||||
1
VERSION
1
VERSION
|
|
@ -1 +0,0 @@
|
||||||
1.8.3
|
|
||||||
|
|
@ -111,9 +111,14 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
|
||||||
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// If the output interface is a struct, make sure names don't collide
|
|
||||||
|
// If the interface is a struct, get of abi->struct_field mapping
|
||||||
|
|
||||||
|
var abi2struct map[string]string
|
||||||
if kind == reflect.Struct {
|
if kind == reflect.Struct {
|
||||||
if err := requireUniqueStructFieldNames(arguments); err != nil {
|
var err error
|
||||||
|
abi2struct, err = mapAbiToStructFields(arguments, value)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,10 +128,11 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
|
||||||
|
|
||||||
switch kind {
|
switch kind {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
err := unpackStruct(value, reflectValue, arg)
|
if structField, ok := abi2struct[arg.Name]; ok {
|
||||||
if err != nil {
|
if err := set(value.FieldByName(structField), reflectValue, arg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
if value.Len() < i {
|
if value.Len() < i {
|
||||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||||
|
|
@ -151,17 +157,22 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interf
|
||||||
if len(marshalledValues) != 1 {
|
if len(marshalledValues) != 1 {
|
||||||
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
|
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
|
||||||
}
|
}
|
||||||
|
|
||||||
elem := reflect.ValueOf(v).Elem()
|
elem := reflect.ValueOf(v).Elem()
|
||||||
kind := elem.Kind()
|
kind := elem.Kind()
|
||||||
reflectValue := reflect.ValueOf(marshalledValues[0])
|
reflectValue := reflect.ValueOf(marshalledValues[0])
|
||||||
|
|
||||||
|
var abi2struct map[string]string
|
||||||
if kind == reflect.Struct {
|
if kind == reflect.Struct {
|
||||||
//make sure names don't collide
|
var err error
|
||||||
if err := requireUniqueStructFieldNames(arguments); err != nil {
|
if abi2struct, err = mapAbiToStructFields(arguments, elem); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
arg := arguments.NonIndexed()[0]
|
||||||
return unpackStruct(elem, reflectValue, arguments[0])
|
if structField, ok := abi2struct[arg.Name]; ok {
|
||||||
|
return set(elem.FieldByName(structField), reflectValue, arg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return set(elem, reflectValue, arguments.NonIndexed()[0])
|
return set(elem, reflectValue, arguments.NonIndexed()[0])
|
||||||
|
|
@ -277,18 +288,3 @@ func capitalise(input string) string {
|
||||||
}
|
}
|
||||||
return strings.ToUpper(input[:1]) + input[1:]
|
return strings.ToUpper(input[:1]) + input[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
//unpackStruct extracts each argument into its corresponding struct field
|
|
||||||
func unpackStruct(value, reflectValue reflect.Value, arg Argument) error {
|
|
||||||
name := capitalise(arg.Name)
|
|
||||||
typ := value.Type()
|
|
||||||
for j := 0; j < typ.NumField(); j++ {
|
|
||||||
// TODO read tags: `abi:"fieldName"`
|
|
||||||
if typ.Field(j).Name == name {
|
|
||||||
if err := set(value.Field(j), reflectValue, arg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
|
@ -65,7 +66,7 @@ type SimulatedBackend struct {
|
||||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||||
// for testing purposes.
|
// for testing purposes.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
|
||||||
database, _ := ethdb.NewMemDatabase()
|
database := ethdb.NewMemDatabase()
|
||||||
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
|
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc}
|
||||||
genesis.MustCommit(database)
|
genesis.MustCommit(database)
|
||||||
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
|
||||||
|
|
@ -159,7 +160,7 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres
|
||||||
|
|
||||||
// TransactionReceipt returns the receipt of a transaction.
|
// TransactionReceipt returns the receipt of a transaction.
|
||||||
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||||
receipt, _, _, _ := core.GetReceipt(b.database, txHash)
|
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash)
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,6 +324,11 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
//
|
//
|
||||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||||
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
|
||||||
|
var filter *filters.Filter
|
||||||
|
if query.BlockHash != nil {
|
||||||
|
// Block filter requested, construct a single-shot filter
|
||||||
|
filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics)
|
||||||
|
} else {
|
||||||
// Initialize unset filter boundaried to run from genesis to chain head
|
// Initialize unset filter boundaried to run from genesis to chain head
|
||||||
from := int64(0)
|
from := int64(0)
|
||||||
if query.FromBlock != nil {
|
if query.FromBlock != nil {
|
||||||
|
|
@ -332,9 +338,10 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter
|
||||||
if query.ToBlock != nil {
|
if query.ToBlock != nil {
|
||||||
to = query.ToBlock.Int64()
|
to = query.ToBlock.Int64()
|
||||||
}
|
}
|
||||||
// Construct and execute the filter
|
// Construct the range filter
|
||||||
filter := filters.New(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
|
filter = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
|
||||||
|
}
|
||||||
|
// Run the filter and return all the logs
|
||||||
logs, err := filter.Logs(ctx)
|
logs, err := filter.Logs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -429,12 +436,24 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb
|
||||||
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
||||||
|
return fb.bc.GetHeaderByHash(hash), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||||
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
|
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
||||||
|
if number == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return rawdb.ReadReceipts(fb.db, hash, *number), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
|
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
|
||||||
receipts := core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash))
|
number := rawdb.ReadHeaderNumber(fb.db, hash)
|
||||||
|
if number == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
receipts := rawdb.ReadReceipts(fb.db, hash, *number)
|
||||||
if receipts == nil {
|
if receipts == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
@ -445,7 +464,7 @@ func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*ty
|
||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
|
func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
||||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
<-quit
|
<-quit
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,15 @@ type Event struct {
|
||||||
Inputs Arguments
|
Inputs Arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
func (event Event) String() string {
|
func (e Event) String() string {
|
||||||
inputs := make([]string, len(event.Inputs))
|
inputs := make([]string, len(e.Inputs))
|
||||||
for i, input := range event.Inputs {
|
for i, input := range e.Inputs {
|
||||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
||||||
if input.Indexed {
|
if input.Indexed {
|
||||||
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
|
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
|
return fmt.Sprintf("e %v(%v)", e.Name, strings.Join(inputs, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id returns the canonical representation of the event's signature used by the
|
// Id returns the canonical representation of the event's signature used by the
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,28 @@ var jsonEventPledge = []byte(`{
|
||||||
"type": "event"
|
"type": "event"
|
||||||
}`)
|
}`)
|
||||||
|
|
||||||
|
var jsonEventMixedCase = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{
|
||||||
|
"indexed": false, "name": "value", "type": "uint256"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "_value", "type": "uint256"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "Value", "type": "uint256"
|
||||||
|
}],
|
||||||
|
"name": "MixedCase",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
// 1000000
|
// 1000000
|
||||||
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||||
|
|
||||||
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||||
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
// 1000000,2218516807680,1000001
|
||||||
|
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||||
|
|
||||||
func TestEventId(t *testing.T) {
|
func TestEventId(t *testing.T) {
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
|
|
@ -121,6 +137,27 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
Value *big.Int
|
Value *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventTransferWithTag struct {
|
||||||
|
// this is valid because `value` is not exportable,
|
||||||
|
// so value is only unmarshalled into `Value1`.
|
||||||
|
value *big.Int
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithSameFieldAndTag struct {
|
||||||
|
Value *big.Int
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithDuplicatedTag struct {
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
Value2 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithEmptyTag struct {
|
||||||
|
Value *big.Int `abi:""`
|
||||||
|
}
|
||||||
|
|
||||||
type EventPledge struct {
|
type EventPledge struct {
|
||||||
Who common.Address
|
Who common.Address
|
||||||
Wad *big.Int
|
Wad *big.Int
|
||||||
|
|
@ -133,9 +170,16 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
Currency [3]byte
|
Currency [3]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventMixedCase struct {
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
Value2 *big.Int `abi:"_value"`
|
||||||
|
Value3 *big.Int `abi:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
bigint := new(big.Int)
|
bigint := new(big.Int)
|
||||||
bigintExpected := big.NewInt(1000000)
|
bigintExpected := big.NewInt(1000000)
|
||||||
bigintExpected2 := big.NewInt(2218516807680)
|
bigintExpected2 := big.NewInt(2218516807680)
|
||||||
|
bigintExpected3 := big.NewInt(1000001)
|
||||||
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||||
var testCases = []struct {
|
var testCases = []struct {
|
||||||
data string
|
data string
|
||||||
|
|
@ -158,6 +202,34 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
jsonEventTransfer,
|
jsonEventTransfer,
|
||||||
"",
|
"",
|
||||||
"Can unpack ERC20 Transfer event into slice",
|
"Can unpack ERC20 Transfer event into slice",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&EventTransferWithTag{},
|
||||||
|
&EventTransferWithTag{Value1: bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into structure with abi: tag",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithDuplicatedTag{},
|
||||||
|
&BadEventTransferWithDuplicatedTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"struct: abi tag in 'Value2' already mapped",
|
||||||
|
"Can not unpack ERC20 Transfer event with duplicated abi tag",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithSameFieldAndTag{},
|
||||||
|
&BadEventTransferWithSameFieldAndTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"abi: multiple variables maps to the same abi field 'value'",
|
||||||
|
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithEmptyTag{},
|
||||||
|
&BadEventTransferWithEmptyTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"struct: abi tag in 'Value' is empty",
|
||||||
|
"Can not unpack ERC20 Transfer event with an empty tag",
|
||||||
}, {
|
}, {
|
||||||
pledgeData1,
|
pledgeData1,
|
||||||
&EventPledge{},
|
&EventPledge{},
|
||||||
|
|
@ -216,6 +288,13 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
jsonEventPledge,
|
jsonEventPledge,
|
||||||
"abi: cannot unmarshal tuple into map[string]interface {}",
|
"abi: cannot unmarshal tuple into map[string]interface {}",
|
||||||
"Can not unpack Pledge event into map",
|
"Can not unpack Pledge event into map",
|
||||||
|
}, {
|
||||||
|
mixedCaseData1,
|
||||||
|
&EventMixedCase{},
|
||||||
|
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
|
||||||
|
jsonEventMixedCase,
|
||||||
|
"",
|
||||||
|
"Can unpack abi variables with mixed case",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
@ -227,7 +306,7 @@ func TestEventTupleUnpack(t *testing.T) {
|
||||||
assert.Nil(err, "Should be able to unpack event data.")
|
assert.Nil(err, "Should be able to unpack event data.")
|
||||||
assert.Equal(tc.expected, tc.dest, tc.name)
|
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||||
} else {
|
} else {
|
||||||
assert.EqualError(err, tc.error)
|
assert.EqualError(err, tc.error, tc.name)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,8 @@ type Method struct {
|
||||||
// Please note that "int" is substitute for its canonical representation "int256"
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
func (method Method) Sig() string {
|
func (method Method) Sig() string {
|
||||||
types := make([]string, len(method.Inputs))
|
types := make([]string, len(method.Inputs))
|
||||||
i := 0
|
for i, input := range method.Inputs {
|
||||||
for _, input := range method.Inputs {
|
|
||||||
types[i] = input.Type.String()
|
types[i] = input.Type.String()
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,35 +25,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
big_t = reflect.TypeOf(&big.Int{})
|
bigT = reflect.TypeOf(&big.Int{})
|
||||||
derefbig_t = reflect.TypeOf(big.Int{})
|
derefbigT = reflect.TypeOf(big.Int{})
|
||||||
uint8_t = reflect.TypeOf(uint8(0))
|
uint8T = reflect.TypeOf(uint8(0))
|
||||||
uint16_t = reflect.TypeOf(uint16(0))
|
uint16T = reflect.TypeOf(uint16(0))
|
||||||
uint32_t = reflect.TypeOf(uint32(0))
|
uint32T = reflect.TypeOf(uint32(0))
|
||||||
uint64_t = reflect.TypeOf(uint64(0))
|
uint64T = reflect.TypeOf(uint64(0))
|
||||||
int_t = reflect.TypeOf(int(0))
|
int8T = reflect.TypeOf(int8(0))
|
||||||
int8_t = reflect.TypeOf(int8(0))
|
int16T = reflect.TypeOf(int16(0))
|
||||||
int16_t = reflect.TypeOf(int16(0))
|
int32T = reflect.TypeOf(int32(0))
|
||||||
int32_t = reflect.TypeOf(int32(0))
|
int64T = reflect.TypeOf(int64(0))
|
||||||
int64_t = reflect.TypeOf(int64(0))
|
addressT = reflect.TypeOf(common.Address{})
|
||||||
address_t = reflect.TypeOf(common.Address{})
|
|
||||||
int_ts = reflect.TypeOf([]int(nil))
|
|
||||||
int8_ts = reflect.TypeOf([]int8(nil))
|
|
||||||
int16_ts = reflect.TypeOf([]int16(nil))
|
|
||||||
int32_ts = reflect.TypeOf([]int32(nil))
|
|
||||||
int64_ts = reflect.TypeOf([]int64(nil))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// U256 converts a big Int into a 256bit EVM number.
|
// U256 converts a big Int into a 256bit EVM number.
|
||||||
func U256(n *big.Int) []byte {
|
func U256(n *big.Int) []byte {
|
||||||
return math.PaddedBigBytes(math.U256(n), 32)
|
return math.PaddedBigBytes(math.U256(n), 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks whether the given reflect value is signed. This also works for slices with a number type
|
|
||||||
func isSigned(v reflect.Value) bool {
|
|
||||||
switch v.Type() {
|
|
||||||
case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ package abi
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -32,13 +31,3 @@ func TestNumberTypes(t *testing.T) {
|
||||||
t.Errorf("expected %x got %x", ubytes, unsigned)
|
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSigned(t *testing.T) {
|
|
||||||
if isSigned(reflect.ValueOf(uint(10))) {
|
|
||||||
t.Error("signed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isSigned(reflect.ValueOf(int(10))) {
|
|
||||||
t.Error("not signed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,13 @@ package abi
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// indirect recursively dereferences the value until it either gets the value
|
// indirect recursively dereferences the value until it either gets the value
|
||||||
// or finds a big.Int
|
// or finds a big.Int
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
func indirect(v reflect.Value) reflect.Value {
|
||||||
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t {
|
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT {
|
||||||
return indirect(v.Elem())
|
return indirect(v.Elem())
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
|
@ -36,26 +37,26 @@ func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type)
|
||||||
switch size {
|
switch size {
|
||||||
case 8:
|
case 8:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint8, uint8_t
|
return reflect.Uint8, uint8T
|
||||||
}
|
}
|
||||||
return reflect.Int8, int8_t
|
return reflect.Int8, int8T
|
||||||
case 16:
|
case 16:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint16, uint16_t
|
return reflect.Uint16, uint16T
|
||||||
}
|
}
|
||||||
return reflect.Int16, int16_t
|
return reflect.Int16, int16T
|
||||||
case 32:
|
case 32:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint32, uint32_t
|
return reflect.Uint32, uint32T
|
||||||
}
|
}
|
||||||
return reflect.Int32, int32_t
|
return reflect.Int32, int32T
|
||||||
case 64:
|
case 64:
|
||||||
if unsigned {
|
if unsigned {
|
||||||
return reflect.Uint64, uint64_t
|
return reflect.Uint64, uint64T
|
||||||
}
|
}
|
||||||
return reflect.Int64, int64_t
|
return reflect.Int64, int64T
|
||||||
}
|
}
|
||||||
return reflect.Ptr, big_t
|
return reflect.Ptr, bigT
|
||||||
}
|
}
|
||||||
|
|
||||||
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
|
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
|
||||||
|
|
@ -111,18 +112,101 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireUniqueStructFieldNames makes sure field names don't collide
|
// mapAbiToStringField maps abi to struct fields.
|
||||||
func requireUniqueStructFieldNames(args Arguments) error {
|
// first round: for each Exportable field that contains a `abi:""` tag
|
||||||
exists := make(map[string]bool)
|
// and this field name exists in the arguments, pair them together.
|
||||||
|
// second round: for each argument field that has not been already linked,
|
||||||
|
// find what variable is expected to be mapped into, if it exists and has not been
|
||||||
|
// used, pair them.
|
||||||
|
func mapAbiToStructFields(args Arguments, value reflect.Value) (map[string]string, error) {
|
||||||
|
|
||||||
|
typ := value.Type()
|
||||||
|
|
||||||
|
abi2struct := make(map[string]string)
|
||||||
|
struct2abi := make(map[string]string)
|
||||||
|
|
||||||
|
// first round ~~~
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
structFieldName := typ.Field(i).Name
|
||||||
|
|
||||||
|
// skip private struct fields.
|
||||||
|
if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip fields that have no abi:"" tag.
|
||||||
|
var ok bool
|
||||||
|
var tagName string
|
||||||
|
if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if tag is empty.
|
||||||
|
if tagName == "" {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check which argument field matches with the abi tag.
|
||||||
|
found := false
|
||||||
|
for _, abiField := range args.NonIndexed() {
|
||||||
|
if abiField.Name == tagName {
|
||||||
|
if abi2struct[abiField.Name] != "" {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
|
||||||
|
}
|
||||||
|
// pair them
|
||||||
|
abi2struct[abiField.Name] = structFieldName
|
||||||
|
struct2abi[structFieldName] = abiField.Name
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this tag has been mapped.
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// second round ~~~
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
field := capitalise(arg.Name)
|
|
||||||
if field == "" {
|
abiFieldName := arg.Name
|
||||||
return fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
structFieldName := capitalise(abiFieldName)
|
||||||
|
|
||||||
|
if structFieldName == "" {
|
||||||
|
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
|
||||||
}
|
}
|
||||||
if exists[field] {
|
|
||||||
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
|
// this abi has already been paired, skip it... unless there exists another, yet unassigned
|
||||||
|
// struct field with the same field name. If so, raise an error:
|
||||||
|
// abi: [ { "name": "value" } ]
|
||||||
|
// struct { Value *big.Int , Value1 *big.Int `abi:"value"`}
|
||||||
|
if abi2struct[abiFieldName] != "" {
|
||||||
|
if abi2struct[abiFieldName] != structFieldName &&
|
||||||
|
struct2abi[structFieldName] == "" &&
|
||||||
|
value.FieldByName(structFieldName).IsValid() {
|
||||||
|
return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", abiFieldName)
|
||||||
}
|
}
|
||||||
exists[field] = true
|
continue
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
// return an error if this struct field has already been paired.
|
||||||
|
if struct2abi[structFieldName] != "" {
|
||||||
|
return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.FieldByName(structFieldName).IsValid() {
|
||||||
|
// pair them
|
||||||
|
abi2struct[abiFieldName] = structFieldName
|
||||||
|
struct2abi[structFieldName] = abiFieldName
|
||||||
|
} else {
|
||||||
|
// not paired, but annotate as used, to detect cases like
|
||||||
|
// abi : [ { "name": "value" }, { "name": "_value" } ]
|
||||||
|
// struct { Value *big.Int }
|
||||||
|
struct2abi[structFieldName] = abiFieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return abi2struct, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ func NewType(t string) (typ Type, err error) {
|
||||||
typ.Type = reflect.TypeOf(bool(false))
|
typ.Type = reflect.TypeOf(bool(false))
|
||||||
case "address":
|
case "address":
|
||||||
typ.Kind = reflect.Array
|
typ.Kind = reflect.Array
|
||||||
typ.Type = address_t
|
typ.Type = addressT
|
||||||
typ.Size = 20
|
typ.Size = 20
|
||||||
typ.T = AddressTy
|
typ.T = AddressTy
|
||||||
case "string":
|
case "string":
|
||||||
|
|
|
||||||
|
|
@ -46,36 +46,36 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
{"bool[2][2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
|
{"bool[2][2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
|
||||||
{"bool[][][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
|
{"bool[][][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
|
||||||
{"bool[][2][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
|
{"bool[][2][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
|
||||||
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
|
{"int8", Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}},
|
||||||
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
|
{"int16", Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}},
|
||||||
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
|
{"int32", Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}},
|
||||||
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
|
{"int64", Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}},
|
||||||
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
|
{"int256", Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}},
|
||||||
{"int8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
{"int8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
||||||
{"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
{"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
||||||
{"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
{"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
||||||
{"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
{"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
||||||
{"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
{"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
||||||
{"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
{"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
||||||
{"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
{"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
||||||
{"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
{"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
||||||
{"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
{"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
||||||
{"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
{"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
||||||
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
|
{"uint8", Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||||
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
|
{"uint16", Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||||
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
|
{"uint32", Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||||
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
|
{"uint64", Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||||
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
|
{"uint256", Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}},
|
||||||
{"uint8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
{"uint8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
||||||
{"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
{"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
||||||
{"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
{"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
||||||
{"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
{"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
||||||
{"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
{"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
||||||
{"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
{"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
||||||
{"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
{"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
||||||
{"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
{"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
||||||
{"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
{"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
||||||
{"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
{"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
||||||
{"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
{"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
||||||
{"bytes[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
{"bytes[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
||||||
{"bytes[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
{"bytes[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
||||||
|
|
@ -84,9 +84,9 @@ func TestTypeRegexp(t *testing.T) {
|
||||||
{"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
{"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
||||||
{"string[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
|
{"string[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
|
||||||
{"string[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
|
{"string[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
|
||||||
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
|
{"address", Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}},
|
||||||
{"address[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
{"address[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
||||||
{"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
{"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
||||||
// TODO when fixed types are implemented properly
|
// TODO when fixed types are implemented properly
|
||||||
// {"fixed", Type{}},
|
// {"fixed", Type{}},
|
||||||
// {"fixed128x128", Type{}},
|
// {"fixed128x128", Type{}},
|
||||||
|
|
@ -252,6 +252,9 @@ func TestTypeCheck(t *testing.T) {
|
||||||
{"bytes20", common.Address{}, ""},
|
{"bytes20", common.Address{}, ""},
|
||||||
{"address", [20]byte{}, ""},
|
{"address", [20]byte{}, ""},
|
||||||
{"address", common.Address{}, ""},
|
{"address", common.Address{}, ""},
|
||||||
|
{"bytes32[]]", "", "invalid arg type in abi"},
|
||||||
|
{"invalidType", "", "unsupported arg type: invalidType"},
|
||||||
|
{"invalidSlice[]", "", "unsupported arg type: invalidSlice"},
|
||||||
} {
|
} {
|
||||||
typ, err := NewType(test.typ)
|
typ, err := NewType(test.typ)
|
||||||
if err != nil && len(test.err) == 0 {
|
if err != nil && len(test.err) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,23 @@ var unpackTests = []unpackTest{
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
want: true,
|
want: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "bool" }]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "bool" }]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000001000000000001",
|
||||||
|
want: false,
|
||||||
|
err: "abi: improperly encoded boolean value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
def: `[{ "type": "bool" }]`,
|
||||||
|
enc: "0000000000000000000000000000000000000000000000000000000000000003",
|
||||||
|
want: false,
|
||||||
|
err: "abi: improperly encoded boolean value",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
def: `[{"type": "uint32"}]`,
|
def: `[{"type": "uint32"}]`,
|
||||||
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
enc: "0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"gopkg.in/fatih/set.v0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
// Minimum amount of time between cache reloads. This limit applies if the platform does
|
||||||
|
|
@ -79,7 +79,7 @@ func newAccountCache(keydir string) (*accountCache, chan struct{}) {
|
||||||
keydir: keydir,
|
keydir: keydir,
|
||||||
byAddr: make(map[common.Address][]accounts.Account),
|
byAddr: make(map[common.Address][]accounts.Account),
|
||||||
notify: make(chan struct{}, 1),
|
notify: make(chan struct{}, 1),
|
||||||
fileC: fileCache{all: set.NewNonTS()},
|
fileC: fileCache{all: mapset.NewThreadUnsafeSet()},
|
||||||
}
|
}
|
||||||
ac.watcher = newWatcher(ac)
|
ac.watcher = newWatcher(ac)
|
||||||
return ac, ac.notify
|
return ac, ac.notify
|
||||||
|
|
@ -237,7 +237,7 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
log.Debug("Failed to reload keystore contents", "err", err)
|
log.Debug("Failed to reload keystore contents", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
|
if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Create a helper method to scan the contents of the key files
|
// Create a helper method to scan the contents of the key files
|
||||||
|
|
@ -272,15 +272,15 @@ func (ac *accountCache) scanAccounts() error {
|
||||||
// Process all the file diffs
|
// Process all the file diffs
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
for _, p := range creates.List() {
|
for _, p := range creates.ToSlice() {
|
||||||
if a := readAccount(p.(string)); a != nil {
|
if a := readAccount(p.(string)); a != nil {
|
||||||
ac.add(*a)
|
ac.add(*a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, p := range deletes.List() {
|
for _, p := range deletes.ToSlice() {
|
||||||
ac.deleteByFile(p.(string))
|
ac.deleteByFile(p.(string))
|
||||||
}
|
}
|
||||||
for _, p := range updates.List() {
|
for _, p := range updates.ToSlice() {
|
||||||
path := p.(string)
|
path := p.(string)
|
||||||
ac.deleteByFile(path)
|
ac.deleteByFile(path)
|
||||||
if a := readAccount(path); a != nil {
|
if a := readAccount(path); a != nil {
|
||||||
|
|
|
||||||
|
|
@ -24,20 +24,20 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
mapset "github.com/deckarep/golang-set"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
set "gopkg.in/fatih/set.v0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileCache is a cache of files seen during scan of keystore.
|
// fileCache is a cache of files seen during scan of keystore.
|
||||||
type fileCache struct {
|
type fileCache struct {
|
||||||
all *set.SetNonTS // Set of all files from the keystore folder
|
all mapset.Set // Set of all files from the keystore folder
|
||||||
lastMod time.Time // Last time instance when a file was modified
|
lastMod time.Time // Last time instance when a file was modified
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// scan performs a new scan on the given directory, compares against the already
|
// scan performs a new scan on the given directory, compares against the already
|
||||||
// cached filenames, and returns file sets: creates, deletes, updates.
|
// cached filenames, and returns file sets: creates, deletes, updates.
|
||||||
func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
|
func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
|
|
||||||
// List all the failes from the keystore folder
|
// List all the failes from the keystore folder
|
||||||
|
|
@ -51,8 +51,8 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
|
||||||
defer fc.mu.Unlock()
|
defer fc.mu.Unlock()
|
||||||
|
|
||||||
// Iterate all the files and gather their metadata
|
// Iterate all the files and gather their metadata
|
||||||
all := set.NewNonTS()
|
all := mapset.NewThreadUnsafeSet()
|
||||||
mods := set.NewNonTS()
|
mods := mapset.NewThreadUnsafeSet()
|
||||||
|
|
||||||
var newLastMod time.Time
|
var newLastMod time.Time
|
||||||
for _, fi := range files {
|
for _, fi := range files {
|
||||||
|
|
@ -76,9 +76,9 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
|
||||||
t2 := time.Now()
|
t2 := time.Now()
|
||||||
|
|
||||||
// Update the tracked files and return the three sets
|
// Update the tracked files and return the three sets
|
||||||
deletes := set.Difference(fc.all, all) // Deletes = previous - current
|
deletes := fc.all.Difference(all) // Deletes = previous - current
|
||||||
creates := set.Difference(all, fc.all) // Creates = current - previous
|
creates := all.Difference(fc.all) // Creates = current - previous
|
||||||
updates := set.Difference(mods, creates) // Updates = modified - creates
|
updates := mods.Difference(creates) // Updates = modified - creates
|
||||||
|
|
||||||
fc.all, fc.lastMod = all, newLastMod
|
fc.all, fc.lastMod = all, newLastMod
|
||||||
t3 := time.Now()
|
t3 := time.Now()
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ var (
|
||||||
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
||||||
|
|
||||||
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
var KeyStoreScheme = "keystore"
|
const KeyStoreScheme = "keystore"
|
||||||
|
|
||||||
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
||||||
const walletRefreshCycle = 3 * time.Second
|
const walletRefreshCycle = 3 * time.Second
|
||||||
|
|
|
||||||
|
|
@ -28,18 +28,18 @@ package keystore
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
crand "crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/crypto/randentropy"
|
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
"golang.org/x/crypto/pbkdf2"
|
||||||
"golang.org/x/crypto/scrypt"
|
"golang.org/x/crypto/scrypt"
|
||||||
|
|
@ -93,7 +93,7 @@ func (ks keyStorePassphrase) GetKey(addr common.Address, filename, auth string)
|
||||||
|
|
||||||
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
// StoreKey generates a key, encrypts with 'auth' and stores in the given directory
|
||||||
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
|
func StoreKey(dir, auth string, scryptN, scryptP int) (common.Address, error) {
|
||||||
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, crand.Reader, auth)
|
_, a, err := storeNewKey(&keyStorePassphrase{dir, scryptN, scryptP}, rand.Reader, auth)
|
||||||
return a.Address, err
|
return a.Address, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,16 +108,19 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er
|
||||||
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||||
if filepath.IsAbs(filename) {
|
if filepath.IsAbs(filename) {
|
||||||
return filename
|
return filename
|
||||||
} else {
|
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
|
||||||
}
|
}
|
||||||
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
||||||
// blob that can be decrypted later on.
|
// blob that can be decrypted later on.
|
||||||
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
authArray := []byte(auth)
|
authArray := []byte(auth)
|
||||||
salt := randentropy.GetEntropyCSPRNG(32)
|
|
||||||
|
salt := make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -125,7 +128,10 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
encryptKey := derivedKey[:16]
|
encryptKey := derivedKey[:16]
|
||||||
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
||||||
|
|
||||||
iv := randentropy.GetEntropyCSPRNG(aes.BlockSize) // 16
|
iv := make([]byte, aes.BlockSize) // 16
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
|
}
|
||||||
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ func (ks keyStorePlain) StoreKey(filename string, key *Key, auth string) error {
|
||||||
func (ks keyStorePlain) JoinPath(filename string) string {
|
func (ks keyStorePlain) JoinPath(filename string) string {
|
||||||
if filepath.IsAbs(filename) {
|
if filepath.IsAbs(filename) {
|
||||||
return filename
|
return filename
|
||||||
} else {
|
}
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,22 @@ func (u URL) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(u.String())
|
return json.Marshal(u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON parses url.
|
||||||
|
func (u *URL) UnmarshalJSON(input []byte) error {
|
||||||
|
var textURL string
|
||||||
|
err := json.Unmarshal(input, &textURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
url, err := parseURL(textURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.Scheme = url.Scheme
|
||||||
|
u.Path = url.Path
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Cmp compares x and y and returns:
|
// Cmp compares x and y and returns:
|
||||||
//
|
//
|
||||||
// -1 if x < y
|
// -1 if x < y
|
||||||
|
|
|
||||||
96
accounts/url_test.go
Normal file
96
accounts/url_test.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLParsing(t *testing.T) {
|
||||||
|
url, err := parseURL("https://ethereum.org")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = parseURL("ethereum.org")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected err, got: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLString(t *testing.T) {
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
if url.String() != "https://ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
url = URL{Scheme: "", Path: "ethereum.org"}
|
||||||
|
if url.String() != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLMarshalJSON(t *testing.T) {
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
json, err := url.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpcted error: %v", err)
|
||||||
|
}
|
||||||
|
if string(json) != "\"https://ethereum.org\"" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLUnmarshalJSON(t *testing.T) {
|
||||||
|
url := &URL{}
|
||||||
|
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpcted error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLComparison(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
urlA URL
|
||||||
|
urlB URL
|
||||||
|
expect int
|
||||||
|
}{
|
||||||
|
{URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0},
|
||||||
|
{URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
{URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1},
|
||||||
|
{URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
result := tt.urlA.Cmp(tt.urlB)
|
||||||
|
if result != tt.expect {
|
||||||
|
t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ func (hub *Hub) refreshWallets() {
|
||||||
// breaking the Ledger protocol if that is waiting for user confirmation. This
|
// breaking the Ledger protocol if that is waiting for user confirmation. This
|
||||||
// is a bug acknowledged at Ledger, but it won't be fixed on old devices so we
|
// is a bug acknowledged at Ledger, but it won't be fixed on old devices so we
|
||||||
// need to prevent concurrent comms ourselves. The more elegant solution would
|
// need to prevent concurrent comms ourselves. The more elegant solution would
|
||||||
// be to ditch enumeration in favor of hutplug events, but that don't work yet
|
// be to ditch enumeration in favor of hotplug events, but that don't work yet
|
||||||
// on Windows so if we need to hack it anyway, this is more elegant for now.
|
// on Windows so if we need to hack it anyway, this is more elegant for now.
|
||||||
hub.commsLock.Lock()
|
hub.commsLock.Lock()
|
||||||
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
if hub.commsPend > 0 { // A confirmation is pending, don't refresh
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func Type(msg proto.Message) uint16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the friendly message type name of a specific protocol buffer
|
// Name returns the friendly message type name of a specific protocol buffer
|
||||||
// type numbers.
|
// type number.
|
||||||
func Name(kind uint16) string {
|
func Name(kind uint16) string {
|
||||||
name := MessageType_name[int32(kind)]
|
name := MessageType_name[int32(kind)]
|
||||||
if len(name) < 12 {
|
if len(name) < 12 {
|
||||||
|
|
|
||||||
|
|
@ -53,11 +53,9 @@ const (
|
||||||
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
||||||
|
|
||||||
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
||||||
ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
||||||
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
||||||
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
||||||
ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
||||||
|
|
@ -304,7 +302,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
for i, component := range derivationPath {
|
for i, component := range derivationPath {
|
||||||
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
||||||
}
|
}
|
||||||
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste
|
// Create the transaction RLP based on whether legacy or EIP155 signing was requested
|
||||||
var (
|
var (
|
||||||
txrlp []byte
|
txrlp []byte
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ type wallet struct {
|
||||||
//
|
//
|
||||||
// As such, a hardware wallet needs two locks to function correctly. A state
|
// As such, a hardware wallet needs two locks to function correctly. A state
|
||||||
// lock can be used to protect the wallet's software-side internal state, which
|
// lock can be used to protect the wallet's software-side internal state, which
|
||||||
// must not be held exlusively during hardware communication. A communication
|
// must not be held exclusively during hardware communication. A communication
|
||||||
// lock can be used to achieve exclusive access to the device itself, this one
|
// lock can be used to achieve exclusive access to the device itself, this one
|
||||||
// however should allow "skipping" waiting for operations that might want to
|
// however should allow "skipping" waiting for operations that might want to
|
||||||
// use the device, but can live without too (e.g. account self-derivation).
|
// use the device, but can live without too (e.g. account self-derivation).
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.3.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.10.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.10.3.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
|
||||||
561
bmt/bmt.go
561
bmt/bmt.go
|
|
@ -1,561 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package bmt provides a binary merkle tree implementation
|
|
||||||
package bmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Binary Merkle Tree Hash is a hash function over arbitrary datachunks of limited size
|
|
||||||
It is defined as the root hash of the binary merkle tree built over fixed size segments
|
|
||||||
of the underlying chunk using any base hash function (e.g keccak 256 SHA3)
|
|
||||||
|
|
||||||
It is used as the chunk hash function in swarm which in turn is the basis for the
|
|
||||||
128 branching swarm hash http://swarm-guide.readthedocs.io/en/latest/architecture.html#swarm-hash
|
|
||||||
|
|
||||||
The BMT is optimal for providing compact inclusion proofs, i.e. prove that a
|
|
||||||
segment is a substring of a chunk starting at a particular offset
|
|
||||||
The size of the underlying segments is fixed at 32 bytes (called the resolution
|
|
||||||
of the BMT hash), the EVM word size to optimize for on-chain BMT verification
|
|
||||||
as well as the hash size optimal for inclusion proofs in the merkle tree of the swarm hash.
|
|
||||||
|
|
||||||
Two implementations are provided:
|
|
||||||
|
|
||||||
* RefHasher is optimized for code simplicity and meant as a reference implementation
|
|
||||||
* Hasher is optimized for speed taking advantage of concurrency with minimalistic
|
|
||||||
control structure to coordinate the concurrent routines
|
|
||||||
It implements the ChunkHash interface as well as the go standard hash.Hash interface
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultSegmentCount is the maximum number of segments of the underlying chunk
|
|
||||||
DefaultSegmentCount = 128 // Should be equal to storage.DefaultBranches
|
|
||||||
// DefaultPoolSize is the maximum number of bmt trees used by the hashers, i.e,
|
|
||||||
// the maximum number of concurrent BMT hashing operations performed by the same hasher
|
|
||||||
DefaultPoolSize = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// BaseHasher is a hash.Hash constructor function used for the base hash of the BMT.
|
|
||||||
type BaseHasher func() hash.Hash
|
|
||||||
|
|
||||||
// Hasher a reusable hasher for fixed maximum size chunks representing a BMT
|
|
||||||
// implements the hash.Hash interface
|
|
||||||
// reuse pool of Tree-s for amortised memory allocation and resource control
|
|
||||||
// supports order-agnostic concurrent segment writes
|
|
||||||
// as well as sequential read and write
|
|
||||||
// can not be called concurrently on more than one chunk
|
|
||||||
// can be further appended after Sum
|
|
||||||
// Reset gives back the Tree to the pool and guaranteed to leave
|
|
||||||
// the tree and itself in a state reusable for hashing a new chunk
|
|
||||||
type Hasher struct {
|
|
||||||
pool *TreePool // BMT resource pool
|
|
||||||
bmt *Tree // prebuilt BMT resource for flowcontrol and proofs
|
|
||||||
blocksize int // segment size (size of hash) also for hash.Hash
|
|
||||||
count int // segment count
|
|
||||||
size int // for hash.Hash same as hashsize
|
|
||||||
cur int // cursor position for righmost currently open chunk
|
|
||||||
segment []byte // the rightmost open segment (not complete)
|
|
||||||
depth int // index of last level
|
|
||||||
result chan []byte // result channel
|
|
||||||
hash []byte // to record the result
|
|
||||||
max int32 // max segments for SegmentWriter interface
|
|
||||||
blockLength []byte // The block length that needes to be added in Sum
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a reusable Hasher
|
|
||||||
// implements the hash.Hash interface
|
|
||||||
// pulls a new Tree from a resource pool for hashing each chunk
|
|
||||||
func New(p *TreePool) *Hasher {
|
|
||||||
return &Hasher{
|
|
||||||
pool: p,
|
|
||||||
depth: depth(p.SegmentCount),
|
|
||||||
size: p.SegmentSize,
|
|
||||||
blocksize: p.SegmentSize,
|
|
||||||
count: p.SegmentCount,
|
|
||||||
result: make(chan []byte),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node is a reuseable segment hasher representing a node in a BMT
|
|
||||||
// it allows for continued writes after a Sum
|
|
||||||
// and is left in completely reusable state after Reset
|
|
||||||
type Node struct {
|
|
||||||
level, index int // position of node for information/logging only
|
|
||||||
initial bool // first and last node
|
|
||||||
root bool // whether the node is root to a smaller BMT
|
|
||||||
isLeft bool // whether it is left side of the parent double segment
|
|
||||||
unbalanced bool // indicates if a node has only the left segment
|
|
||||||
parent *Node // BMT connections
|
|
||||||
state int32 // atomic increment impl concurrent boolean toggle
|
|
||||||
left, right []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNode constructor for segment hasher nodes in the BMT
|
|
||||||
func NewNode(level, index int, parent *Node) *Node {
|
|
||||||
return &Node{
|
|
||||||
parent: parent,
|
|
||||||
level: level,
|
|
||||||
index: index,
|
|
||||||
initial: index == 0,
|
|
||||||
isLeft: index%2 == 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TreePool provides a pool of Trees used as resources by Hasher
|
|
||||||
// a Tree popped from the pool is guaranteed to have clean state
|
|
||||||
// for hashing a new chunk
|
|
||||||
// Hasher Reset releases the Tree to the pool
|
|
||||||
type TreePool struct {
|
|
||||||
lock sync.Mutex
|
|
||||||
c chan *Tree
|
|
||||||
hasher BaseHasher
|
|
||||||
SegmentSize int
|
|
||||||
SegmentCount int
|
|
||||||
Capacity int
|
|
||||||
count int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTreePool creates a Tree pool with hasher, segment size, segment count and capacity
|
|
||||||
// on GetTree it reuses free Trees or creates a new one if size is not reached
|
|
||||||
func NewTreePool(hasher BaseHasher, segmentCount, capacity int) *TreePool {
|
|
||||||
return &TreePool{
|
|
||||||
c: make(chan *Tree, capacity),
|
|
||||||
hasher: hasher,
|
|
||||||
SegmentSize: hasher().Size(),
|
|
||||||
SegmentCount: segmentCount,
|
|
||||||
Capacity: capacity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drain drains the pool uptil it has no more than n resources
|
|
||||||
func (self *TreePool) Drain(n int) {
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
for len(self.c) > n {
|
|
||||||
<-self.c
|
|
||||||
self.count--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reserve is blocking until it returns an available Tree
|
|
||||||
// it reuses free Trees or creates a new one if size is not reached
|
|
||||||
func (self *TreePool) Reserve() *Tree {
|
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
var t *Tree
|
|
||||||
if self.count == self.Capacity {
|
|
||||||
return <-self.c
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case t = <-self.c:
|
|
||||||
default:
|
|
||||||
t = NewTree(self.hasher, self.SegmentSize, self.SegmentCount)
|
|
||||||
self.count++
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release gives back a Tree to the pool.
|
|
||||||
// This Tree is guaranteed to be in reusable state
|
|
||||||
// does not need locking
|
|
||||||
func (self *TreePool) Release(t *Tree) {
|
|
||||||
self.c <- t // can never fail but...
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tree is a reusable control structure representing a BMT
|
|
||||||
// organised in a binary tree
|
|
||||||
// Hasher uses a TreePool to pick one for each chunk hash
|
|
||||||
// the Tree is 'locked' while not in the pool
|
|
||||||
type Tree struct {
|
|
||||||
leaves []*Node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw draws the BMT (badly)
|
|
||||||
func (self *Tree) Draw(hash []byte, d int) string {
|
|
||||||
var left, right []string
|
|
||||||
var anc []*Node
|
|
||||||
for i, n := range self.leaves {
|
|
||||||
left = append(left, fmt.Sprintf("%v", hashstr(n.left)))
|
|
||||||
if i%2 == 0 {
|
|
||||||
anc = append(anc, n.parent)
|
|
||||||
}
|
|
||||||
right = append(right, fmt.Sprintf("%v", hashstr(n.right)))
|
|
||||||
}
|
|
||||||
anc = self.leaves
|
|
||||||
var hashes [][]string
|
|
||||||
for l := 0; len(anc) > 0; l++ {
|
|
||||||
var nodes []*Node
|
|
||||||
hash := []string{""}
|
|
||||||
for i, n := range anc {
|
|
||||||
hash = append(hash, fmt.Sprintf("%v|%v", hashstr(n.left), hashstr(n.right)))
|
|
||||||
if i%2 == 0 && n.parent != nil {
|
|
||||||
nodes = append(nodes, n.parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hash = append(hash, "")
|
|
||||||
hashes = append(hashes, hash)
|
|
||||||
anc = nodes
|
|
||||||
}
|
|
||||||
hashes = append(hashes, []string{"", fmt.Sprintf("%v", hashstr(hash)), ""})
|
|
||||||
total := 60
|
|
||||||
del := " "
|
|
||||||
var rows []string
|
|
||||||
for i := len(hashes) - 1; i >= 0; i-- {
|
|
||||||
var textlen int
|
|
||||||
hash := hashes[i]
|
|
||||||
for _, s := range hash {
|
|
||||||
textlen += len(s)
|
|
||||||
}
|
|
||||||
if total < textlen {
|
|
||||||
total = textlen + len(hash)
|
|
||||||
}
|
|
||||||
delsize := (total - textlen) / (len(hash) - 1)
|
|
||||||
if delsize > len(del) {
|
|
||||||
delsize = len(del)
|
|
||||||
}
|
|
||||||
row := fmt.Sprintf("%v: %v", len(hashes)-i-1, strings.Join(hash, del[:delsize]))
|
|
||||||
rows = append(rows, row)
|
|
||||||
|
|
||||||
}
|
|
||||||
rows = append(rows, strings.Join(left, " "))
|
|
||||||
rows = append(rows, strings.Join(right, " "))
|
|
||||||
return strings.Join(rows, "\n") + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTree initialises the Tree by building up the nodes of a BMT
|
|
||||||
// segment size is stipulated to be the size of the hash
|
|
||||||
// segmentCount needs to be positive integer and does not need to be
|
|
||||||
// a power of two and can even be an odd number
|
|
||||||
// segmentSize * segmentCount determines the maximum chunk size
|
|
||||||
// hashed using the tree
|
|
||||||
func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree {
|
|
||||||
n := NewNode(0, 0, nil)
|
|
||||||
n.root = true
|
|
||||||
prevlevel := []*Node{n}
|
|
||||||
// iterate over levels and creates 2^level nodes
|
|
||||||
level := 1
|
|
||||||
count := 2
|
|
||||||
for d := 1; d <= depth(segmentCount); d++ {
|
|
||||||
nodes := make([]*Node, count)
|
|
||||||
for i := 0; i < len(nodes); i++ {
|
|
||||||
parent := prevlevel[i/2]
|
|
||||||
t := NewNode(level, i, parent)
|
|
||||||
nodes[i] = t
|
|
||||||
}
|
|
||||||
prevlevel = nodes
|
|
||||||
level++
|
|
||||||
count *= 2
|
|
||||||
}
|
|
||||||
// the datanode level is the nodes on the last level where
|
|
||||||
return &Tree{
|
|
||||||
leaves: prevlevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods needed by hash.Hash
|
|
||||||
|
|
||||||
// Size returns the size
|
|
||||||
func (self *Hasher) Size() int {
|
|
||||||
return self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockSize returns the block size
|
|
||||||
func (self *Hasher) BlockSize() int {
|
|
||||||
return self.blocksize
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum returns the hash of the buffer
|
|
||||||
// hash.Hash interface Sum method appends the byte slice to the underlying
|
|
||||||
// data before it calculates and returns the hash of the chunk
|
|
||||||
func (self *Hasher) Sum(b []byte) (r []byte) {
|
|
||||||
t := self.bmt
|
|
||||||
i := self.cur
|
|
||||||
n := t.leaves[i]
|
|
||||||
j := i
|
|
||||||
// must run strictly before all nodes calculate
|
|
||||||
// datanodes are guaranteed to have a parent
|
|
||||||
if len(self.segment) > self.size && i > 0 && n.parent != nil {
|
|
||||||
n = n.parent
|
|
||||||
} else {
|
|
||||||
i *= 2
|
|
||||||
}
|
|
||||||
d := self.finalise(n, i)
|
|
||||||
self.writeSegment(j, self.segment, d)
|
|
||||||
c := <-self.result
|
|
||||||
self.releaseTree()
|
|
||||||
|
|
||||||
// sha3(length + BMT(pure_chunk))
|
|
||||||
if self.blockLength == nil {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
res := self.pool.hasher()
|
|
||||||
res.Reset()
|
|
||||||
res.Write(self.blockLength)
|
|
||||||
res.Write(c)
|
|
||||||
return res.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the SwarmHash interface
|
|
||||||
|
|
||||||
// Hash waits for the hasher result and returns it
|
|
||||||
// caller must call this on a BMT Hasher being written to
|
|
||||||
func (self *Hasher) Hash() []byte {
|
|
||||||
return <-self.result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the io.Writer interface
|
|
||||||
|
|
||||||
// Write fills the buffer to hash
|
|
||||||
// with every full segment complete launches a hasher go routine
|
|
||||||
// that shoots up the BMT
|
|
||||||
func (self *Hasher) Write(b []byte) (int, error) {
|
|
||||||
l := len(b)
|
|
||||||
if l <= 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
s := self.segment
|
|
||||||
i := self.cur
|
|
||||||
count := (self.count + 1) / 2
|
|
||||||
need := self.count*self.size - self.cur*2*self.size
|
|
||||||
size := self.size
|
|
||||||
if need > size {
|
|
||||||
size *= 2
|
|
||||||
}
|
|
||||||
if l < need {
|
|
||||||
need = l
|
|
||||||
}
|
|
||||||
// calculate missing bit to complete current open segment
|
|
||||||
rest := size - len(s)
|
|
||||||
if need < rest {
|
|
||||||
rest = need
|
|
||||||
}
|
|
||||||
s = append(s, b[:rest]...)
|
|
||||||
need -= rest
|
|
||||||
// read full segments and the last possibly partial segment
|
|
||||||
for need > 0 && i < count-1 {
|
|
||||||
// push all finished chunks we read
|
|
||||||
self.writeSegment(i, s, self.depth)
|
|
||||||
need -= size
|
|
||||||
if need < 0 {
|
|
||||||
size += need
|
|
||||||
}
|
|
||||||
s = b[rest : rest+size]
|
|
||||||
rest += size
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
self.segment = s
|
|
||||||
self.cur = i
|
|
||||||
// otherwise, we can assume len(s) == 0, so all buffer is read and chunk is not yet full
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the io.ReaderFrom interface
|
|
||||||
|
|
||||||
// ReadFrom reads from io.Reader and appends to the data to hash using Write
|
|
||||||
// it reads so that chunk to hash is maximum length or reader reaches EOF
|
|
||||||
// caller must Reset the hasher prior to call
|
|
||||||
func (self *Hasher) ReadFrom(r io.Reader) (m int64, err error) {
|
|
||||||
bufsize := self.size*self.count - self.size*self.cur - len(self.segment)
|
|
||||||
buf := make([]byte, bufsize)
|
|
||||||
var read int
|
|
||||||
for {
|
|
||||||
var n int
|
|
||||||
n, err = r.Read(buf)
|
|
||||||
read += n
|
|
||||||
if err == io.EOF || read == len(buf) {
|
|
||||||
hash := self.Sum(buf[:n])
|
|
||||||
if read == len(buf) {
|
|
||||||
err = NewEOC(hash)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
n, err = self.Write(buf[:n])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return int64(read), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset needs to be called before writing to the hasher
|
|
||||||
func (self *Hasher) Reset() {
|
|
||||||
self.getTree()
|
|
||||||
self.blockLength = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher implements the SwarmHash interface
|
|
||||||
|
|
||||||
// ResetWithLength needs to be called before writing to the hasher
|
|
||||||
// the argument is supposed to be the byte slice binary representation of
|
|
||||||
// the legth of the data subsumed under the hash
|
|
||||||
func (self *Hasher) ResetWithLength(l []byte) {
|
|
||||||
self.Reset()
|
|
||||||
self.blockLength = l
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release gives back the Tree to the pool whereby it unlocks
|
|
||||||
// it resets tree, segment and index
|
|
||||||
func (self *Hasher) releaseTree() {
|
|
||||||
if self.bmt != nil {
|
|
||||||
n := self.bmt.leaves[self.cur]
|
|
||||||
for ; n != nil; n = n.parent {
|
|
||||||
n.unbalanced = false
|
|
||||||
if n.parent != nil {
|
|
||||||
n.root = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.pool.Release(self.bmt)
|
|
||||||
self.bmt = nil
|
|
||||||
|
|
||||||
}
|
|
||||||
self.cur = 0
|
|
||||||
self.segment = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Hasher) writeSegment(i int, s []byte, d int) {
|
|
||||||
h := self.pool.hasher()
|
|
||||||
n := self.bmt.leaves[i]
|
|
||||||
|
|
||||||
if len(s) > self.size && n.parent != nil {
|
|
||||||
go func() {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(s)
|
|
||||||
s = h.Sum(nil)
|
|
||||||
|
|
||||||
if n.root {
|
|
||||||
self.result <- s
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.run(n.parent, h, d, n.index, s)
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go self.run(n, h, d, i*2, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *Hasher) run(n *Node, h hash.Hash, d int, i int, s []byte) {
|
|
||||||
isLeft := i%2 == 0
|
|
||||||
for {
|
|
||||||
if isLeft {
|
|
||||||
n.left = s
|
|
||||||
} else {
|
|
||||||
n.right = s
|
|
||||||
}
|
|
||||||
if !n.unbalanced && n.toggle() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !n.unbalanced || !isLeft || i == 0 && d == 0 {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(n.left)
|
|
||||||
h.Write(n.right)
|
|
||||||
s = h.Sum(nil)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
s = append(n.left, n.right...)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hash = s
|
|
||||||
if n.root {
|
|
||||||
self.result <- s
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isLeft = n.isLeft
|
|
||||||
n = n.parent
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTree obtains a BMT resource by reserving one from the pool
|
|
||||||
func (self *Hasher) getTree() *Tree {
|
|
||||||
if self.bmt != nil {
|
|
||||||
return self.bmt
|
|
||||||
}
|
|
||||||
t := self.pool.Reserve()
|
|
||||||
self.bmt = t
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// atomic bool toggle implementing a concurrent reusable 2-state object
|
|
||||||
// atomic addint with %2 implements atomic bool toggle
|
|
||||||
// it returns true if the toggler just put it in the active/waiting state
|
|
||||||
func (self *Node) toggle() bool {
|
|
||||||
return atomic.AddInt32(&self.state, 1)%2 == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashstr(b []byte) string {
|
|
||||||
end := len(b)
|
|
||||||
if end > 4 {
|
|
||||||
end = 4
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%x", b[:end])
|
|
||||||
}
|
|
||||||
|
|
||||||
func depth(n int) (d int) {
|
|
||||||
for l := (n - 1) / 2; l > 0; l /= 2 {
|
|
||||||
d++
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// finalise is following the zigzags on the tree belonging
|
|
||||||
// to the final datasegment
|
|
||||||
func (self *Hasher) finalise(n *Node, i int) (d int) {
|
|
||||||
isLeft := i%2 == 0
|
|
||||||
for {
|
|
||||||
// when the final segment's path is going via left segments
|
|
||||||
// the incoming data is pushed to the parent upon pulling the left
|
|
||||||
// we do not need toogle the state since this condition is
|
|
||||||
// detectable
|
|
||||||
n.unbalanced = isLeft
|
|
||||||
n.right = nil
|
|
||||||
if n.initial {
|
|
||||||
n.root = true
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
isLeft = n.isLeft
|
|
||||||
n = n.parent
|
|
||||||
d++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOC (end of chunk) implements the error interface
|
|
||||||
type EOC struct {
|
|
||||||
Hash []byte // read the hash of the chunk off the error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the error string
|
|
||||||
func (self *EOC) Error() string {
|
|
||||||
return fmt.Sprintf("hasher limit reached, chunk hash: %x", self.Hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEOC creates new end of chunk error with the hash
|
|
||||||
func NewEOC(hash []byte) *EOC {
|
|
||||||
return &EOC{hash}
|
|
||||||
}
|
|
||||||
85
bmt/bmt_r.go
85
bmt/bmt_r.go
|
|
@ -1,85 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// simple nonconcurrent reference implementation for hashsize segment based
|
|
||||||
// Binary Merkle tree hash on arbitrary but fixed maximum chunksize
|
|
||||||
//
|
|
||||||
// This implementation does not take advantage of any paralellisms and uses
|
|
||||||
// far more memory than necessary, but it is easy to see that it is correct.
|
|
||||||
// It can be used for generating test cases for optimized implementations.
|
|
||||||
// see testBMTHasherCorrectness function in bmt_test.go
|
|
||||||
package bmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RefHasher is the non-optimized easy to read reference implementation of BMT
|
|
||||||
type RefHasher struct {
|
|
||||||
span int
|
|
||||||
section int
|
|
||||||
cap int
|
|
||||||
h hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRefHasher returns a new RefHasher
|
|
||||||
func NewRefHasher(hasher BaseHasher, count int) *RefHasher {
|
|
||||||
h := hasher()
|
|
||||||
hashsize := h.Size()
|
|
||||||
maxsize := hashsize * count
|
|
||||||
c := 2
|
|
||||||
for ; c < count; c *= 2 {
|
|
||||||
}
|
|
||||||
if c > 2 {
|
|
||||||
c /= 2
|
|
||||||
}
|
|
||||||
return &RefHasher{
|
|
||||||
section: 2 * hashsize,
|
|
||||||
span: c * hashsize,
|
|
||||||
cap: maxsize,
|
|
||||||
h: h,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash returns the BMT hash of the byte slice
|
|
||||||
// implements the SwarmHash interface
|
|
||||||
func (rh *RefHasher) Hash(d []byte) []byte {
|
|
||||||
if len(d) > rh.cap {
|
|
||||||
d = d[:rh.cap]
|
|
||||||
}
|
|
||||||
|
|
||||||
return rh.hash(d, rh.span)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rh *RefHasher) hash(d []byte, s int) []byte {
|
|
||||||
l := len(d)
|
|
||||||
left := d
|
|
||||||
var right []byte
|
|
||||||
if l > rh.section {
|
|
||||||
for ; s >= l; s /= 2 {
|
|
||||||
}
|
|
||||||
left = rh.hash(d[:s], s)
|
|
||||||
right = d[s:]
|
|
||||||
if l-s > rh.section/2 {
|
|
||||||
right = rh.hash(right, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer rh.h.Reset()
|
|
||||||
rh.h.Write(left)
|
|
||||||
rh.h.Write(right)
|
|
||||||
h := rh.h.Sum(nil)
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
481
bmt/bmt_test.go
481
bmt/bmt_test.go
|
|
@ -1,481 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package bmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
crand "crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxproccnt = 8
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestRefHasher tests that the RefHasher computes the expected BMT hash for
|
|
||||||
// all data lengths between 0 and 256 bytes
|
|
||||||
func TestRefHasher(t *testing.T) {
|
|
||||||
hashFunc := sha3.NewKeccak256
|
|
||||||
|
|
||||||
sha3 := func(data ...[]byte) []byte {
|
|
||||||
h := hashFunc()
|
|
||||||
for _, v := range data {
|
|
||||||
h.Write(v)
|
|
||||||
}
|
|
||||||
return h.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the test struct is used to specify the expected BMT hash for data
|
|
||||||
// lengths between "from" and "to"
|
|
||||||
type test struct {
|
|
||||||
from int64
|
|
||||||
to int64
|
|
||||||
expected func([]byte) []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests []*test
|
|
||||||
|
|
||||||
// all lengths in [0,64] should be:
|
|
||||||
//
|
|
||||||
// sha3(data)
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 0,
|
|
||||||
to: 64,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(data)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [65,96] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// data[64:]
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 65,
|
|
||||||
to: 96,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(data[:64]), data[64:])
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [97,128] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:])
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 97,
|
|
||||||
to: 128,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(data[:64]), sha3(data[64:]))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [129,160] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// data[128:]
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 129,
|
|
||||||
to: 160,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), data[128:])
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [161,192] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// sha3(data[128:])
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 161,
|
|
||||||
to: 192,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(data[128:]))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [193,224] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[128:192])
|
|
||||||
// data[192:]
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 193,
|
|
||||||
to: 224,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), data[192:]))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// all lengths in [225,256] should be:
|
|
||||||
//
|
|
||||||
// sha3(
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[:64])
|
|
||||||
// sha3(data[64:128])
|
|
||||||
// )
|
|
||||||
// sha3(
|
|
||||||
// sha3(data[128:192])
|
|
||||||
// sha3(data[192:])
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
tests = append(tests, &test{
|
|
||||||
from: 225,
|
|
||||||
to: 256,
|
|
||||||
expected: func(data []byte) []byte {
|
|
||||||
return sha3(sha3(sha3(data[:64]), sha3(data[64:128])), sha3(sha3(data[128:192]), sha3(data[192:])))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// run the tests
|
|
||||||
for _, x := range tests {
|
|
||||||
for length := x.from; length <= x.to; length++ {
|
|
||||||
t.Run(fmt.Sprintf("%d_bytes", length), func(t *testing.T) {
|
|
||||||
data := make([]byte, length)
|
|
||||||
if _, err := io.ReadFull(crand.Reader, data); err != nil && err != io.EOF {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expected := x.expected(data)
|
|
||||||
actual := NewRefHasher(hashFunc, 128).Hash(data)
|
|
||||||
if !bytes.Equal(actual, expected) {
|
|
||||||
t.Fatalf("expected %x, got %x", expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDataReader(l int) (r io.Reader) {
|
|
||||||
return io.LimitReader(crand.Reader, int64(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherCorrectness(t *testing.T) {
|
|
||||||
err := testHasher(testBaseHasher)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHasher(f func(BaseHasher, []byte, int, int) error) error {
|
|
||||||
tdata := testDataReader(4128)
|
|
||||||
data := make([]byte, 4128)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
size := hasher().Size()
|
|
||||||
counts := []int{1, 2, 3, 4, 5, 8, 16, 32, 64, 128}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for _, count := range counts {
|
|
||||||
max := count * size
|
|
||||||
incr := 1
|
|
||||||
for n := 0; n <= max+incr; n += incr {
|
|
||||||
err = f(hasher, data, n, count)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherReuseWithoutRelease(t *testing.T) {
|
|
||||||
testHasherReuse(1, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherReuseWithRelease(t *testing.T) {
|
|
||||||
testHasherReuse(maxproccnt, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHasherReuse(i int, t *testing.T) {
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
pool := NewTreePool(hasher, 128, i)
|
|
||||||
defer pool.Drain(0)
|
|
||||||
bmt := New(pool)
|
|
||||||
|
|
||||||
for i := 0; i < 500; i++ {
|
|
||||||
n := rand.Intn(4096)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
|
|
||||||
err := testHasherCorrectness(bmt, hasher, data, n, 128)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasherConcurrency(t *testing.T) {
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
pool := NewTreePool(hasher, 128, maxproccnt)
|
|
||||||
defer pool.Drain(0)
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
cycles := 100
|
|
||||||
wg.Add(maxproccnt * cycles)
|
|
||||||
errc := make(chan error)
|
|
||||||
|
|
||||||
for p := 0; p < maxproccnt; p++ {
|
|
||||||
for i := 0; i < cycles; i++ {
|
|
||||||
go func() {
|
|
||||||
bmt := New(pool)
|
|
||||||
n := rand.Intn(4096)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
err := testHasherCorrectness(bmt, hasher, data, n, 128)
|
|
||||||
wg.Done()
|
|
||||||
if err != nil {
|
|
||||||
errc <- err
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(errc)
|
|
||||||
}()
|
|
||||||
var err error
|
|
||||||
select {
|
|
||||||
case <-time.NewTimer(5 * time.Second).C:
|
|
||||||
err = fmt.Errorf("timed out")
|
|
||||||
case err = <-errc:
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testBaseHasher(hasher BaseHasher, d []byte, n, count int) error {
|
|
||||||
pool := NewTreePool(hasher, count, 1)
|
|
||||||
defer pool.Drain(0)
|
|
||||||
bmt := New(pool)
|
|
||||||
return testHasherCorrectness(bmt, hasher, d, n, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testHasherCorrectness(bmt hash.Hash, hasher BaseHasher, d []byte, n, count int) (err error) {
|
|
||||||
data := d[:n]
|
|
||||||
rbmt := NewRefHasher(hasher, count)
|
|
||||||
exp := rbmt.Hash(data)
|
|
||||||
timeout := time.NewTimer(time.Second)
|
|
||||||
c := make(chan error)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
bmt.Reset()
|
|
||||||
bmt.Write(data)
|
|
||||||
got := bmt.Sum(nil)
|
|
||||||
if !bytes.Equal(got, exp) {
|
|
||||||
c <- fmt.Errorf("wrong hash: expected %x, got %x", exp, got)
|
|
||||||
}
|
|
||||||
close(c)
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case <-timeout.C:
|
|
||||||
err = fmt.Errorf("BMT hash calculation timed out")
|
|
||||||
case err = <-c:
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSHA3_4k(t *testing.B) { benchmarkSHA3(4096, t) }
|
|
||||||
func BenchmarkSHA3_2k(t *testing.B) { benchmarkSHA3(4096/2, t) }
|
|
||||||
func BenchmarkSHA3_1k(t *testing.B) { benchmarkSHA3(4096/4, t) }
|
|
||||||
func BenchmarkSHA3_512b(t *testing.B) { benchmarkSHA3(4096/8, t) }
|
|
||||||
func BenchmarkSHA3_256b(t *testing.B) { benchmarkSHA3(4096/16, t) }
|
|
||||||
func BenchmarkSHA3_128b(t *testing.B) { benchmarkSHA3(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkBMTBaseline_4k(t *testing.B) { benchmarkBMTBaseline(4096, t) }
|
|
||||||
func BenchmarkBMTBaseline_2k(t *testing.B) { benchmarkBMTBaseline(4096/2, t) }
|
|
||||||
func BenchmarkBMTBaseline_1k(t *testing.B) { benchmarkBMTBaseline(4096/4, t) }
|
|
||||||
func BenchmarkBMTBaseline_512b(t *testing.B) { benchmarkBMTBaseline(4096/8, t) }
|
|
||||||
func BenchmarkBMTBaseline_256b(t *testing.B) { benchmarkBMTBaseline(4096/16, t) }
|
|
||||||
func BenchmarkBMTBaseline_128b(t *testing.B) { benchmarkBMTBaseline(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkRefHasher_4k(t *testing.B) { benchmarkRefHasher(4096, t) }
|
|
||||||
func BenchmarkRefHasher_2k(t *testing.B) { benchmarkRefHasher(4096/2, t) }
|
|
||||||
func BenchmarkRefHasher_1k(t *testing.B) { benchmarkRefHasher(4096/4, t) }
|
|
||||||
func BenchmarkRefHasher_512b(t *testing.B) { benchmarkRefHasher(4096/8, t) }
|
|
||||||
func BenchmarkRefHasher_256b(t *testing.B) { benchmarkRefHasher(4096/16, t) }
|
|
||||||
func BenchmarkRefHasher_128b(t *testing.B) { benchmarkRefHasher(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkHasher_4k(t *testing.B) { benchmarkHasher(4096, t) }
|
|
||||||
func BenchmarkHasher_2k(t *testing.B) { benchmarkHasher(4096/2, t) }
|
|
||||||
func BenchmarkHasher_1k(t *testing.B) { benchmarkHasher(4096/4, t) }
|
|
||||||
func BenchmarkHasher_512b(t *testing.B) { benchmarkHasher(4096/8, t) }
|
|
||||||
func BenchmarkHasher_256b(t *testing.B) { benchmarkHasher(4096/16, t) }
|
|
||||||
func BenchmarkHasher_128b(t *testing.B) { benchmarkHasher(4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkHasherNoReuse_4k(t *testing.B) { benchmarkHasherReuse(1, 4096, t) }
|
|
||||||
func BenchmarkHasherNoReuse_2k(t *testing.B) { benchmarkHasherReuse(1, 4096/2, t) }
|
|
||||||
func BenchmarkHasherNoReuse_1k(t *testing.B) { benchmarkHasherReuse(1, 4096/4, t) }
|
|
||||||
func BenchmarkHasherNoReuse_512b(t *testing.B) { benchmarkHasherReuse(1, 4096/8, t) }
|
|
||||||
func BenchmarkHasherNoReuse_256b(t *testing.B) { benchmarkHasherReuse(1, 4096/16, t) }
|
|
||||||
func BenchmarkHasherNoReuse_128b(t *testing.B) { benchmarkHasherReuse(1, 4096/32, t) }
|
|
||||||
|
|
||||||
func BenchmarkHasherReuse_4k(t *testing.B) { benchmarkHasherReuse(16, 4096, t) }
|
|
||||||
func BenchmarkHasherReuse_2k(t *testing.B) { benchmarkHasherReuse(16, 4096/2, t) }
|
|
||||||
func BenchmarkHasherReuse_1k(t *testing.B) { benchmarkHasherReuse(16, 4096/4, t) }
|
|
||||||
func BenchmarkHasherReuse_512b(t *testing.B) { benchmarkHasherReuse(16, 4096/8, t) }
|
|
||||||
func BenchmarkHasherReuse_256b(t *testing.B) { benchmarkHasherReuse(16, 4096/16, t) }
|
|
||||||
func BenchmarkHasherReuse_128b(t *testing.B) { benchmarkHasherReuse(16, 4096/32, t) }
|
|
||||||
|
|
||||||
// benchmarks the minimum hashing time for a balanced (for simplicity) BMT
|
|
||||||
// by doing count/segmentsize parallel hashings of 2*segmentsize bytes
|
|
||||||
// doing it on n maxproccnt each reusing the base hasher
|
|
||||||
// the premise is that this is the minimum computation needed for a BMT
|
|
||||||
// therefore this serves as a theoretical optimum for concurrent implementations
|
|
||||||
func benchmarkBMTBaseline(n int, t *testing.B) {
|
|
||||||
tdata := testDataReader(64)
|
|
||||||
data := make([]byte, 64)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
count := int32((n-1)/hasher().Size() + 1)
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(maxproccnt)
|
|
||||||
var i int32
|
|
||||||
for j := 0; j < maxproccnt; j++ {
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
h := hasher()
|
|
||||||
for atomic.AddInt32(&i, 1) < count {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(data)
|
|
||||||
h.Sum(nil)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkHasher(n int, t *testing.B) {
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
|
|
||||||
size := 1
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
segmentCount := 128
|
|
||||||
pool := NewTreePool(hasher, segmentCount, size)
|
|
||||||
bmt := New(pool)
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
bmt.Reset()
|
|
||||||
bmt.Write(data)
|
|
||||||
bmt.Sum(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkHasherReuse(poolsize, n int, t *testing.B) {
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata.Read(data)
|
|
||||||
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
segmentCount := 128
|
|
||||||
pool := NewTreePool(hasher, segmentCount, poolsize)
|
|
||||||
cycles := 200
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(cycles)
|
|
||||||
for j := 0; j < cycles; j++ {
|
|
||||||
bmt := New(pool)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
bmt.Reset()
|
|
||||||
bmt.Write(data)
|
|
||||||
bmt.Sum(nil)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkSHA3(n int, t *testing.B) {
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
h := hasher()
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(data)
|
|
||||||
h.Sum(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkRefHasher(n int, t *testing.B) {
|
|
||||||
data := make([]byte, n)
|
|
||||||
tdata := testDataReader(n)
|
|
||||||
tdata.Read(data)
|
|
||||||
hasher := sha3.NewKeccak256
|
|
||||||
rbmt := NewRefHasher(hasher, 128)
|
|
||||||
|
|
||||||
t.ReportAllocs()
|
|
||||||
t.ResetTimer()
|
|
||||||
for i := 0; i < t.N; i++ {
|
|
||||||
rbmt.Hash(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
203
build/ci.go
203
build/ci.go
|
|
@ -26,7 +26,7 @@ Available commands are:
|
||||||
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
install [ -arch architecture ] [ -cc compiler ] [ packages... ] -- builds packages and executables
|
||||||
test [ -coverage ] [ packages... ] -- runs the tests
|
test [ -coverage ] [ packages... ] -- runs the tests
|
||||||
lint -- runs certain pre-selected linters
|
lint -- runs certain pre-selected linters
|
||||||
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artefacts
|
archive [ -arch architecture ] [ -type zip|tar ] [ -signer key-envvar ] [ -upload dest ] -- archives build artifacts
|
||||||
importkeys -- imports signing keys from env
|
importkeys -- imports signing keys from env
|
||||||
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
|
debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package
|
||||||
nsis -- creates a Windows NSIS installer
|
nsis -- creates a Windows NSIS installer
|
||||||
|
|
@ -59,6 +59,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/internal/build"
|
"github.com/ethereum/go-ethereum/internal/build"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
sv "github.com/ethereum/go-ethereum/swarm/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -77,52 +79,81 @@ var (
|
||||||
executablePath("geth"),
|
executablePath("geth"),
|
||||||
executablePath("puppeth"),
|
executablePath("puppeth"),
|
||||||
executablePath("rlpdump"),
|
executablePath("rlpdump"),
|
||||||
executablePath("swarm"),
|
|
||||||
executablePath("wnode"),
|
executablePath("wnode"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Files that end up in the swarm*.zip archive.
|
||||||
|
swarmArchiveFiles = []string{
|
||||||
|
"COPYING",
|
||||||
|
executablePath("swarm"),
|
||||||
|
}
|
||||||
|
|
||||||
// A debian package is created for all executables listed here.
|
// A debian package is created for all executables listed here.
|
||||||
debExecutables = []debExecutable{
|
debExecutables = []debExecutable{
|
||||||
{
|
{
|
||||||
Name: "abigen",
|
BinaryName: "abigen",
|
||||||
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
Description: "Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bootnode",
|
BinaryName: "bootnode",
|
||||||
Description: "Ethereum bootnode.",
|
Description: "Ethereum bootnode.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "evm",
|
BinaryName: "evm",
|
||||||
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
Description: "Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "geth",
|
BinaryName: "geth",
|
||||||
Description: "Ethereum CLI client.",
|
Description: "Ethereum CLI client.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "puppeth",
|
BinaryName: "puppeth",
|
||||||
Description: "Ethereum private network manager.",
|
Description: "Ethereum private network manager.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "rlpdump",
|
BinaryName: "rlpdump",
|
||||||
Description: "Developer utility tool that prints RLP structures.",
|
Description: "Developer utility tool that prints RLP structures.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "swarm",
|
BinaryName: "wnode",
|
||||||
Description: "Ethereum Swarm daemon and tools",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "wnode",
|
|
||||||
Description: "Ethereum Whisper diagnostic tool",
|
Description: "Ethereum Whisper diagnostic tool",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A debian package is created for all executables listed here.
|
||||||
|
debSwarmExecutables = []debExecutable{
|
||||||
|
{
|
||||||
|
BinaryName: "swarm",
|
||||||
|
PackageName: "ethereum-swarm",
|
||||||
|
Description: "Ethereum Swarm daemon and tools",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
debEthereum = debPackage{
|
||||||
|
Name: "ethereum",
|
||||||
|
Version: params.Version,
|
||||||
|
Executables: debExecutables,
|
||||||
|
}
|
||||||
|
|
||||||
|
debSwarm = debPackage{
|
||||||
|
Name: "ethereum-swarm",
|
||||||
|
Version: sv.Version,
|
||||||
|
Executables: debSwarmExecutables,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debian meta packages to build and push to Ubuntu PPA
|
||||||
|
debPackages = []debPackage{
|
||||||
|
debSwarm,
|
||||||
|
debEthereum,
|
||||||
|
}
|
||||||
|
|
||||||
// Distros for which packages are created.
|
// Distros for which packages are created.
|
||||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||||
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
||||||
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
||||||
// Note: zesty is unsupported because it was officially deprecated on lanchpad.
|
// Note: zesty is unsupported because it was officially deprecated on lanchpad.
|
||||||
debDistros = []string{"trusty", "xenial", "artful", "bionic"}
|
// Note: artful is unsupported because it was officially deprecated on lanchpad.
|
||||||
|
debDistros = []string{"trusty", "xenial", "bionic", "cosmic"}
|
||||||
)
|
)
|
||||||
|
|
||||||
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
|
||||||
|
|
@ -331,7 +362,11 @@ func doLint(cmdline []string) {
|
||||||
// Run fast linters batched together
|
// Run fast linters batched together
|
||||||
configs := []string{
|
configs := []string{
|
||||||
"--vendor",
|
"--vendor",
|
||||||
|
"--tests",
|
||||||
|
"--deadline=2m",
|
||||||
"--disable-all",
|
"--disable-all",
|
||||||
|
"--enable=goimports",
|
||||||
|
"--enable=varcheck",
|
||||||
"--enable=vet",
|
"--enable=vet",
|
||||||
"--enable=gofmt",
|
"--enable=gofmt",
|
||||||
"--enable=misspell",
|
"--enable=misspell",
|
||||||
|
|
@ -342,13 +377,12 @@ func doLint(cmdline []string) {
|
||||||
|
|
||||||
// Run slow linters one by one
|
// Run slow linters one by one
|
||||||
for _, linter := range []string{"unconvert", "gosimple"} {
|
for _, linter := range []string{"unconvert", "gosimple"} {
|
||||||
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
configs = []string{"--vendor", "--tests", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
||||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release Packaging
|
// Release Packaging
|
||||||
|
|
||||||
func doArchive(cmdline []string) {
|
func doArchive(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
arch = flag.String("arch", runtime.GOARCH, "Architecture cross packaging")
|
||||||
|
|
@ -369,9 +403,13 @@ func doArchive(cmdline []string) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
env = build.Env()
|
env = build.Env()
|
||||||
base = archiveBasename(*arch, env)
|
|
||||||
geth = "geth-" + base + ext
|
basegeth = archiveBasename(*arch, params.ArchiveVersion(env.Commit))
|
||||||
alltools = "geth-alltools-" + base + ext
|
geth = "geth-" + basegeth + ext
|
||||||
|
alltools = "geth-alltools-" + basegeth + ext
|
||||||
|
|
||||||
|
baseswarm = archiveBasename(*arch, sv.ArchiveVersion(env.Commit))
|
||||||
|
swarm = "swarm-" + baseswarm + ext
|
||||||
)
|
)
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
if err := build.WriteArchive(geth, gethArchiveFiles); err != nil {
|
||||||
|
|
@ -380,14 +418,17 @@ func doArchive(cmdline []string) {
|
||||||
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, archive := range []string{geth, alltools} {
|
if err := build.WriteArchive(swarm, swarmArchiveFiles); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, archive := range []string{geth, alltools, swarm} {
|
||||||
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveBasename(arch string, env build.Environment) string {
|
func archiveBasename(arch string, archiveVersion string) string {
|
||||||
platform := runtime.GOOS + "-" + arch
|
platform := runtime.GOOS + "-" + arch
|
||||||
if arch == "arm" {
|
if arch == "arm" {
|
||||||
platform += os.Getenv("GOARM")
|
platform += os.Getenv("GOARM")
|
||||||
|
|
@ -398,18 +439,7 @@ func archiveBasename(arch string, env build.Environment) string {
|
||||||
if arch == "ios" {
|
if arch == "ios" {
|
||||||
platform = "ios-all"
|
platform = "ios-all"
|
||||||
}
|
}
|
||||||
return platform + "-" + archiveVersion(env)
|
return platform + "-" + archiveVersion
|
||||||
}
|
|
||||||
|
|
||||||
func archiveVersion(env build.Environment) string {
|
|
||||||
version := build.VERSION()
|
|
||||||
if isUnstableBuild(env) {
|
|
||||||
version += "-unstable"
|
|
||||||
}
|
|
||||||
if env.Commit != "" {
|
|
||||||
version += "-" + env.Commit[:8]
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveUpload(archive string, blobstore string, signer string) error {
|
func archiveUpload(archive string, blobstore string, signer string) error {
|
||||||
|
|
@ -459,7 +489,6 @@ func maybeSkipArchive(env build.Environment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debian Packaging
|
// Debian Packaging
|
||||||
|
|
||||||
func doDebianSource(cmdline []string) {
|
func doDebianSource(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
signer = flag.String("signer", "", `Signing key name, also used as package author`)
|
||||||
|
|
@ -483,9 +512,10 @@ func doDebianSource(cmdline []string) {
|
||||||
build.MustRun(gpg)
|
build.MustRun(gpg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the packages.
|
// Create Debian packages and upload them
|
||||||
|
for _, pkg := range debPackages {
|
||||||
for _, distro := range debDistros {
|
for _, distro := range debDistros {
|
||||||
meta := newDebMetadata(distro, *signer, env, now)
|
meta := newDebMetadata(distro, *signer, env, now, pkg.Name, pkg.Version, pkg.Executables)
|
||||||
pkgdir := stageDebianSource(*workdir, meta)
|
pkgdir := stageDebianSource(*workdir, meta)
|
||||||
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
|
debuild := exec.Command("debuild", "-S", "-sa", "-us", "-uc")
|
||||||
debuild.Dir = pkgdir
|
debuild.Dir = pkgdir
|
||||||
|
|
@ -501,6 +531,7 @@ func doDebianSource(cmdline []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makeWorkdir(wdflag string) string {
|
func makeWorkdir(wdflag string) string {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -522,9 +553,17 @@ func isUnstableBuild(env build.Environment) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type debPackage struct {
|
||||||
|
Name string // the name of the Debian package to produce, e.g. "ethereum", or "ethereum-swarm"
|
||||||
|
Version string // the clean version of the debPackage, e.g. 1.8.12 or 0.3.0, without any metadata
|
||||||
|
Executables []debExecutable // executables to be included in the package
|
||||||
|
}
|
||||||
|
|
||||||
type debMetadata struct {
|
type debMetadata struct {
|
||||||
Env build.Environment
|
Env build.Environment
|
||||||
|
|
||||||
|
PackageName string
|
||||||
|
|
||||||
// go-ethereum version being built. Note that this
|
// go-ethereum version being built. Note that this
|
||||||
// is not the debian package version. The package version
|
// is not the debian package version. The package version
|
||||||
// is constructed by VersionString.
|
// is constructed by VersionString.
|
||||||
|
|
@ -536,21 +575,33 @@ type debMetadata struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type debExecutable struct {
|
type debExecutable struct {
|
||||||
Name, Description string
|
PackageName string
|
||||||
|
BinaryName string
|
||||||
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDebMetadata(distro, author string, env build.Environment, t time.Time) debMetadata {
|
// Package returns the name of the package if present, or
|
||||||
|
// fallbacks to BinaryName
|
||||||
|
func (d debExecutable) Package() string {
|
||||||
|
if d.PackageName != "" {
|
||||||
|
return d.PackageName
|
||||||
|
}
|
||||||
|
return d.BinaryName
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDebMetadata(distro, author string, env build.Environment, t time.Time, name string, version string, exes []debExecutable) debMetadata {
|
||||||
if author == "" {
|
if author == "" {
|
||||||
// No signing key, use default author.
|
// No signing key, use default author.
|
||||||
author = "Ethereum Builds <fjl@ethereum.org>"
|
author = "Ethereum Builds <fjl@ethereum.org>"
|
||||||
}
|
}
|
||||||
return debMetadata{
|
return debMetadata{
|
||||||
|
PackageName: name,
|
||||||
Env: env,
|
Env: env,
|
||||||
Author: author,
|
Author: author,
|
||||||
Distro: distro,
|
Distro: distro,
|
||||||
Version: build.VERSION(),
|
Version: version,
|
||||||
Time: t.Format(time.RFC1123Z),
|
Time: t.Format(time.RFC1123Z),
|
||||||
Executables: debExecutables,
|
Executables: exes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -558,9 +609,9 @@ func newDebMetadata(distro, author string, env build.Environment, t time.Time) d
|
||||||
// on all executable packages.
|
// on all executable packages.
|
||||||
func (meta debMetadata) Name() string {
|
func (meta debMetadata) Name() string {
|
||||||
if isUnstableBuild(meta.Env) {
|
if isUnstableBuild(meta.Env) {
|
||||||
return "ethereum-unstable"
|
return meta.PackageName + "-unstable"
|
||||||
}
|
}
|
||||||
return "ethereum"
|
return meta.PackageName
|
||||||
}
|
}
|
||||||
|
|
||||||
// VersionString returns the debian version of the packages.
|
// VersionString returns the debian version of the packages.
|
||||||
|
|
@ -587,9 +638,20 @@ func (meta debMetadata) ExeList() string {
|
||||||
// ExeName returns the package name of an executable package.
|
// ExeName returns the package name of an executable package.
|
||||||
func (meta debMetadata) ExeName(exe debExecutable) string {
|
func (meta debMetadata) ExeName(exe debExecutable) string {
|
||||||
if isUnstableBuild(meta.Env) {
|
if isUnstableBuild(meta.Env) {
|
||||||
return exe.Name + "-unstable"
|
return exe.Package() + "-unstable"
|
||||||
}
|
}
|
||||||
return exe.Name
|
return exe.Package()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthereumSwarmPackageName returns the name of the swarm package based on
|
||||||
|
// environment, e.g. "ethereum-swarm-unstable", or "ethereum-swarm".
|
||||||
|
// This is needed so that we make sure that "ethereum" package,
|
||||||
|
// depends on and installs "ethereum-swarm"
|
||||||
|
func (meta debMetadata) EthereumSwarmPackageName() string {
|
||||||
|
if isUnstableBuild(meta.Env) {
|
||||||
|
return debSwarm.Name + "-unstable"
|
||||||
|
}
|
||||||
|
return debSwarm.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExeConflicts returns the content of the Conflicts field
|
// ExeConflicts returns the content of the Conflicts field
|
||||||
|
|
@ -604,7 +666,7 @@ func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||||
// be preferred and the conflicting files should be handled via
|
// be preferred and the conflicting files should be handled via
|
||||||
// alternates. We might do this eventually but using a conflict is
|
// alternates. We might do this eventually but using a conflict is
|
||||||
// easier now.
|
// easier now.
|
||||||
return "ethereum, " + exe.Name
|
return "ethereum, " + exe.Package()
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -621,24 +683,23 @@ func stageDebianSource(tmpdir string, meta debMetadata) (pkgdir string) {
|
||||||
|
|
||||||
// Put the debian build files in place.
|
// Put the debian build files in place.
|
||||||
debian := filepath.Join(pkgdir, "debian")
|
debian := filepath.Join(pkgdir, "debian")
|
||||||
build.Render("build/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.rules", filepath.Join(debian, "rules"), 0755, meta)
|
||||||
build.Render("build/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.changelog", filepath.Join(debian, "changelog"), 0644, meta)
|
||||||
build.Render("build/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.control", filepath.Join(debian, "control"), 0644, meta)
|
||||||
build.Render("build/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
build.Render("build/deb/"+meta.PackageName+"/deb.copyright", filepath.Join(debian, "copyright"), 0644, meta)
|
||||||
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
build.RenderString("8\n", filepath.Join(debian, "compat"), 0644, meta)
|
||||||
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
build.RenderString("3.0 (native)\n", filepath.Join(debian, "source/format"), 0644, meta)
|
||||||
for _, exe := range meta.Executables {
|
for _, exe := range meta.Executables {
|
||||||
install := filepath.Join(debian, meta.ExeName(exe)+".install")
|
install := filepath.Join(debian, meta.ExeName(exe)+".install")
|
||||||
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
|
docs := filepath.Join(debian, meta.ExeName(exe)+".docs")
|
||||||
build.Render("build/deb.install", install, 0644, exe)
|
build.Render("build/deb/"+meta.PackageName+"/deb.install", install, 0644, exe)
|
||||||
build.Render("build/deb.docs", docs, 0644, exe)
|
build.Render("build/deb/"+meta.PackageName+"/deb.docs", docs, 0644, exe)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgdir
|
return pkgdir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windows installer
|
// Windows installer
|
||||||
|
|
||||||
func doWindowsInstaller(cmdline []string) {
|
func doWindowsInstaller(cmdline []string) {
|
||||||
// Parse the flags and make skip installer generation on PRs
|
// Parse the flags and make skip installer generation on PRs
|
||||||
var (
|
var (
|
||||||
|
|
@ -688,11 +749,11 @@ func doWindowsInstaller(cmdline []string) {
|
||||||
// Build the installer. This assumes that all the needed files have been previously
|
// Build the installer. This assumes that all the needed files have been previously
|
||||||
// built (don't mix building and packaging to keep cross compilation complexity to a
|
// built (don't mix building and packaging to keep cross compilation complexity to a
|
||||||
// minimum).
|
// minimum).
|
||||||
version := strings.Split(build.VERSION(), ".")
|
version := strings.Split(params.Version, ".")
|
||||||
if env.Commit != "" {
|
if env.Commit != "" {
|
||||||
version[2] += "-" + env.Commit[:8]
|
version[2] += "-" + env.Commit[:8]
|
||||||
}
|
}
|
||||||
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, env) + ".exe")
|
installer, _ := filepath.Abs("geth-" + archiveBasename(*arch, params.ArchiveVersion(env.Commit)) + ".exe")
|
||||||
build.MustRunCommand("makensis.exe",
|
build.MustRunCommand("makensis.exe",
|
||||||
"/DOUTPUTFILE="+installer,
|
"/DOUTPUTFILE="+installer,
|
||||||
"/DMAJORVERSION="+version[0],
|
"/DMAJORVERSION="+version[0],
|
||||||
|
|
@ -730,7 +791,7 @@ func doAndroidArchive(cmdline []string) {
|
||||||
// Build the Android archive and Maven resources
|
// Build the Android archive and Maven resources
|
||||||
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
||||||
build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK")))
|
build.MustRun(gomobileTool("init", "--ndk", os.Getenv("ANDROID_NDK")))
|
||||||
build.MustRun(gomobileTool("bind", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
|
build.MustRun(gomobileTool("bind", "-ldflags", "-s -w", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile"))
|
||||||
|
|
||||||
if *local {
|
if *local {
|
||||||
// If we're building locally, copy bundle to build dir and skip Maven
|
// If we're building locally, copy bundle to build dir and skip Maven
|
||||||
|
|
@ -744,7 +805,7 @@ func doAndroidArchive(cmdline []string) {
|
||||||
maybeSkipArchive(env)
|
maybeSkipArchive(env)
|
||||||
|
|
||||||
// Sign and upload the archive to Azure
|
// Sign and upload the archive to Azure
|
||||||
archive := "geth-" + archiveBasename("android", env) + ".aar"
|
archive := "geth-" + archiveBasename("android", params.ArchiveVersion(env.Commit)) + ".aar"
|
||||||
os.Rename("geth.aar", archive)
|
os.Rename("geth.aar", archive)
|
||||||
|
|
||||||
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
if err := archiveUpload(archive, *upload, *signer); err != nil {
|
||||||
|
|
@ -754,7 +815,7 @@ func doAndroidArchive(cmdline []string) {
|
||||||
os.Rename(archive, meta.Package+".aar")
|
os.Rename(archive, meta.Package+".aar")
|
||||||
if *signer != "" && *deploy != "" {
|
if *signer != "" && *deploy != "" {
|
||||||
// Import the signing key into the local GPG instance
|
// Import the signing key into the local GPG instance
|
||||||
if b64key := os.Getenv(*signer); b64key != "" {
|
b64key := os.Getenv(*signer)
|
||||||
key, err := base64.StdEncoding.DecodeString(b64key)
|
key, err := base64.StdEncoding.DecodeString(b64key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("invalid base64 %s", *signer)
|
log.Fatalf("invalid base64 %s", *signer)
|
||||||
|
|
@ -762,14 +823,19 @@ func doAndroidArchive(cmdline []string) {
|
||||||
gpg := exec.Command("gpg", "--import")
|
gpg := exec.Command("gpg", "--import")
|
||||||
gpg.Stdin = bytes.NewReader(key)
|
gpg.Stdin = bytes.NewReader(key)
|
||||||
build.MustRun(gpg)
|
build.MustRun(gpg)
|
||||||
|
|
||||||
|
keyID, err := build.PGPKeyID(string(key))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
// Upload the artifacts to Sonatype and/or Maven Central
|
// Upload the artifacts to Sonatype and/or Maven Central
|
||||||
repo := *deploy + "/service/local/staging/deploy/maven2"
|
repo := *deploy + "/service/local/staging/deploy/maven2"
|
||||||
if meta.Develop {
|
if meta.Develop {
|
||||||
repo = *deploy + "/content/repositories/snapshots"
|
repo = *deploy + "/content/repositories/snapshots"
|
||||||
}
|
}
|
||||||
build.MustRunCommand("mvn", "gpg:sign-and-deploy-file",
|
build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", "-e", "-X",
|
||||||
"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
|
"-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh",
|
||||||
|
"-Dgpg.keyname="+keyID,
|
||||||
"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
|
"-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -824,7 +890,7 @@ func newMavenMetadata(env build.Environment) mavenMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Render the version and package strings
|
// Render the version and package strings
|
||||||
version := build.VERSION()
|
version := params.Version
|
||||||
if isUnstableBuild(env) {
|
if isUnstableBuild(env) {
|
||||||
version += "-SNAPSHOT"
|
version += "-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
|
@ -851,7 +917,7 @@ func doXCodeFramework(cmdline []string) {
|
||||||
// Build the iOS XCode framework
|
// Build the iOS XCode framework
|
||||||
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind"))
|
||||||
build.MustRun(gomobileTool("init"))
|
build.MustRun(gomobileTool("init"))
|
||||||
bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "-v", "github.com/ethereum/go-ethereum/mobile")
|
bind := gomobileTool("bind", "-ldflags", "-s -w", "--target", "ios", "--tags", "ios", "-v", "github.com/ethereum/go-ethereum/mobile")
|
||||||
|
|
||||||
if *local {
|
if *local {
|
||||||
// If we're building locally, use the build folder and stop afterwards
|
// If we're building locally, use the build folder and stop afterwards
|
||||||
|
|
@ -859,7 +925,7 @@ func doXCodeFramework(cmdline []string) {
|
||||||
build.MustRun(bind)
|
build.MustRun(bind)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
archive := "geth-" + archiveBasename("ios", env)
|
archive := "geth-" + archiveBasename("ios", params.ArchiveVersion(env.Commit))
|
||||||
if err := os.Mkdir(archive, os.ModePerm); err != nil {
|
if err := os.Mkdir(archive, os.ModePerm); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -915,7 +981,7 @@ func newPodMetadata(env build.Environment, archive string) podMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version := build.VERSION()
|
version := params.Version
|
||||||
if isUnstableBuild(env) {
|
if isUnstableBuild(env) {
|
||||||
version += "-unstable." + env.Buildnum
|
version += "-unstable." + env.Buildnum
|
||||||
}
|
}
|
||||||
|
|
@ -1011,23 +1077,14 @@ func doPurge(cmdline []string) {
|
||||||
}
|
}
|
||||||
for i := 0; i < len(blobs); i++ {
|
for i := 0; i < len(blobs); i++ {
|
||||||
for j := i + 1; j < len(blobs); j++ {
|
for j := i + 1; j < len(blobs); j++ {
|
||||||
iTime, err := time.Parse(time.RFC1123, blobs[i].Properties.LastModified)
|
if blobs[i].Properties.LastModified.After(blobs[j].Properties.LastModified) {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
jTime, err := time.Parse(time.RFC1123, blobs[j].Properties.LastModified)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if iTime.After(jTime) {
|
|
||||||
blobs[i], blobs[j] = blobs[j], blobs[i]
|
blobs[i], blobs[j] = blobs[j], blobs[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Filter out all archives more recent that the given threshold
|
// Filter out all archives more recent that the given threshold
|
||||||
for i, blob := range blobs {
|
for i, blob := range blobs {
|
||||||
timestamp, _ := time.Parse(time.RFC1123, blob.Properties.LastModified)
|
if time.Since(blob.Properties.LastModified) < time.Duration(*limit)*24*time.Hour {
|
||||||
if time.Since(timestamp) < time.Duration(*limit)*24*time.Hour {
|
|
||||||
blobs = blobs[:i]
|
blobs = blobs[:i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
build/clean_go_build_cache.sh
Executable file
19
build/clean_go_build_cache.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Cleaning the Go cache only makes sense if we actually have Go installed... or
|
||||||
|
# if Go is actually callable. This does not hold true during deb packaging, so
|
||||||
|
# we need an explicit check to avoid build failures.
|
||||||
|
if ! command -v go > /dev/null; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
version_gt() {
|
||||||
|
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
golang_version=$(go version |cut -d' ' -f3 |sed 's/go//')
|
||||||
|
|
||||||
|
# Clean go build cache when go version is greater than or equal to 1.10
|
||||||
|
if !(version_gt 1.10 $golang_version); then
|
||||||
|
go clean -cache
|
||||||
|
fi
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
build/bin/{{.Name}} usr/bin
|
|
||||||
19
build/deb/ethereum-swarm/deb.control
Normal file
19
build/deb/ethereum-swarm/deb.control
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Source: {{.Name}}
|
||||||
|
Section: science
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: {{.Author}}
|
||||||
|
Build-Depends: debhelper (>= 8.0.0), golang-1.10
|
||||||
|
Standards-Version: 3.9.5
|
||||||
|
Homepage: https://ethereum.org
|
||||||
|
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||||
|
Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
|
|
||||||
|
{{range .Executables}}
|
||||||
|
Package: {{$.ExeName .}}
|
||||||
|
Conflicts: {{$.ExeConflicts .}}
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Built-Using: ${misc:Built-Using}
|
||||||
|
Description: {{.Description}}
|
||||||
|
{{.Description}}
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright 2016 The go-ethereum Authors
|
Copyright 2018 The go-ethereum Authors
|
||||||
|
|
||||||
go-ethereum is free software: you can redistribute it and/or modify
|
go-ethereum is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
1
build/deb/ethereum-swarm/deb.install
Normal file
1
build/deb/ethereum-swarm/deb.install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build/bin/{{.BinaryName}} usr/bin
|
||||||
5
build/deb/ethereum/deb.changelog
Normal file
5
build/deb/ethereum/deb.changelog
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||||
|
|
||||||
|
* git build of {{.Env.Commit}}
|
||||||
|
|
||||||
|
-- {{.Author}} {{.Time}}
|
||||||
|
|
@ -10,9 +10,9 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
|
|
||||||
Package: {{.Name}}
|
Package: {{.Name}}
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${misc:Depends}, {{.ExeList}}
|
Depends: ${misc:Depends}, {{.EthereumSwarmPackageName}}, {{.ExeList}}
|
||||||
Description: Meta-package to install geth and other tools
|
Description: Meta-package to install geth, swarm, and other tools
|
||||||
Meta-package to install geth and other tools
|
Meta-package to install geth, swarm and other tools
|
||||||
|
|
||||||
{{range .Executables}}
|
{{range .Executables}}
|
||||||
Package: {{$.ExeName .}}
|
Package: {{$.ExeName .}}
|
||||||
14
build/deb/ethereum/deb.copyright
Normal file
14
build/deb/ethereum/deb.copyright
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Copyright 2018 The go-ethereum Authors
|
||||||
|
|
||||||
|
go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
1
build/deb/ethereum/deb.docs
Normal file
1
build/deb/ethereum/deb.docs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
AUTHORS
|
||||||
1
build/deb/ethereum/deb.install
Normal file
1
build/deb/ethereum/deb.install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
build/bin/{{.BinaryName}} usr/bin
|
||||||
13
build/deb/ethereum/deb.rules
Normal file
13
build/deb/ethereum/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.10/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
18
build/goimports.sh
Executable file
18
build/goimports.sh
Executable file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
find_files() {
|
||||||
|
find . ! \( \
|
||||||
|
\( \
|
||||||
|
-path '.github' \
|
||||||
|
-o -path './build/_workspace' \
|
||||||
|
-o -path './build/bin' \
|
||||||
|
-o -path './crypto/bn256' \
|
||||||
|
-o -path '*/vendor/*' \
|
||||||
|
\) -prune \
|
||||||
|
\) -name '*.go'
|
||||||
|
}
|
||||||
|
|
||||||
|
GOFMT="gofmt -s -w"
|
||||||
|
GOIMPORTS="goimports -w"
|
||||||
|
find_files | xargs $GOFMT
|
||||||
|
find_files | xargs $GOIMPORTS
|
||||||
|
|
@ -29,7 +29,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind")
|
abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind, - for STDIN")
|
||||||
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)")
|
binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)")
|
||||||
typFlag = flag.String("type", "", "Struct name for the binding (default = package name)")
|
typFlag = flag.String("type", "", "Struct name for the binding (default = package name)")
|
||||||
|
|
||||||
|
|
@ -75,17 +75,28 @@ func main() {
|
||||||
bins []string
|
bins []string
|
||||||
types []string
|
types []string
|
||||||
)
|
)
|
||||||
if *solFlag != "" {
|
if *solFlag != "" || *abiFlag == "-" {
|
||||||
// Generate the list of types to exclude from binding
|
// Generate the list of types to exclude from binding
|
||||||
exclude := make(map[string]bool)
|
exclude := make(map[string]bool)
|
||||||
for _, kind := range strings.Split(*excFlag, ",") {
|
for _, kind := range strings.Split(*excFlag, ",") {
|
||||||
exclude[strings.ToLower(kind)] = true
|
exclude[strings.ToLower(kind)] = true
|
||||||
}
|
}
|
||||||
contracts, err := compiler.CompileSolidity(*solcFlag, *solFlag)
|
|
||||||
|
var contracts map[string]*compiler.Contract
|
||||||
|
var err error
|
||||||
|
if *solFlag != "" {
|
||||||
|
contracts, err = compiler.CompileSolidity(*solcFlag, *solFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to build Solidity contract: %v\n", err)
|
fmt.Printf("Failed to build Solidity contract: %v\n", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
contracts, err = contractsFromStdin()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to read input ABIs from STDIN: %v\n", err)
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Gather all non-excluded contract for binding
|
// Gather all non-excluded contract for binding
|
||||||
for name, contract := range contracts {
|
for name, contract := range contracts {
|
||||||
if exclude[strings.ToLower(name)] {
|
if exclude[strings.ToLower(name)] {
|
||||||
|
|
@ -138,3 +149,12 @@ func main() {
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func contractsFromStdin() (map[string]*compiler.Contract, error) {
|
||||||
|
bytes, err := ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return compiler.ParseCombinedJSON(bytes, "", "", "", "")
|
||||||
|
}
|
||||||
|
|
|
||||||
1
cmd/clef/4byte.json
Normal file
1
cmd/clef/4byte.json
Normal file
File diff suppressed because one or more lines are too long
877
cmd/clef/README.md
Normal file
877
cmd/clef/README.md
Normal file
|
|
@ -0,0 +1,877 @@
|
||||||
|
Clef
|
||||||
|
----
|
||||||
|
Clef can be used to sign transactions and data and is meant as a replacement for geth's account management.
|
||||||
|
This allows DApps not to depend on geth's account management. When a DApp wants to sign data it can send the data to
|
||||||
|
the signer, the signer will then provide the user with context and asks the user for permission to sign the data. If
|
||||||
|
the users grants the signing request the signer will send the signature back to the DApp.
|
||||||
|
|
||||||
|
This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can
|
||||||
|
help in situations when a DApp is connected to a remote node because a local Ethereum node is not available, not
|
||||||
|
synchronised with the chain or a particular Ethereum node that has no built-in (or limited) account management.
|
||||||
|
|
||||||
|
Clef can run as a daemon on the same machine, or off a usb-stick like [usb armory](https://inversepath.com/usbarmory),
|
||||||
|
or a separate VM in a [QubesOS](https://www.qubes-os.org/) type os setup.
|
||||||
|
|
||||||
|
Check out
|
||||||
|
|
||||||
|
* the [tutorial](tutorial.md) for some concrete examples on how the signer works.
|
||||||
|
* the [setup docs](docs/setup.md) for some information on how to configure it to work on QubesOS or USBArmory.
|
||||||
|
|
||||||
|
|
||||||
|
## Command line flags
|
||||||
|
Clef accepts the following command line options:
|
||||||
|
```
|
||||||
|
COMMANDS:
|
||||||
|
init Initialize the signer, generate secret storage
|
||||||
|
attest Attest that a js-file is to be used
|
||||||
|
addpw Store a credential for a keystore file
|
||||||
|
help Shows a list of commands or help for one command
|
||||||
|
|
||||||
|
GLOBAL OPTIONS:
|
||||||
|
--loglevel value log level to emit to the screen (default: 4)
|
||||||
|
--keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore")
|
||||||
|
--configdir value Directory for clef configuration (default: "$HOME/.clef")
|
||||||
|
--networkid value Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby) (default: 1)
|
||||||
|
--lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength
|
||||||
|
--nousb Disables monitoring for and managing USB hardware wallets
|
||||||
|
--rpcaddr value HTTP-RPC server listening interface (default: "localhost")
|
||||||
|
--rpcport value HTTP-RPC server listening port (default: 8550)
|
||||||
|
--signersecret value A file containing the password used to encrypt signer credentials, e.g. keystore credentials and ruleset hash
|
||||||
|
--4bytedb value File containing 4byte-identifiers (default: "./4byte.json")
|
||||||
|
--4bytedb-custom value File used for writing new 4byte-identifiers submitted via API (default: "./4byte-custom.json")
|
||||||
|
--auditlog value File used to emit audit logs. Set to "" to disable (default: "audit.log")
|
||||||
|
--rules value Enable rule-engine (default: "rules.json")
|
||||||
|
--stdio-ui Use STDIN/STDOUT as a channel for an external UI. This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user interface, and can be used when the signer is started by an external process.
|
||||||
|
--stdio-ui-test Mechanism to test interface between signer and UI. Requires 'stdio-ui'.
|
||||||
|
--help, -h show help
|
||||||
|
--version, -v print the version
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
signer -keystore /my/keystore -chainid 4
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Security model
|
||||||
|
|
||||||
|
The security model of the signer is as follows:
|
||||||
|
|
||||||
|
* One critical component (the signer binary / daemon) is responsible for handling cryptographic operations: signing, private keys, encryption/decryption of keystore files.
|
||||||
|
* The signer binary has a well-defined 'external' API.
|
||||||
|
* The 'external' API is considered UNTRUSTED.
|
||||||
|
* The signer binary also communicates with whatever process that invoked the binary, via stdin/stdout.
|
||||||
|
* This channel is considered 'trusted'. Over this channel, approvals and passwords are communicated.
|
||||||
|
|
||||||
|
The general flow for signing a transaction using e.g. geth is as follows:
|
||||||
|

|
||||||
|
|
||||||
|
In this case, `geth` would be started with `--externalsigner=http://localhost:8550` and would relay requests to `eth.sendTransaction`.
|
||||||
|
|
||||||
|
## TODOs
|
||||||
|
|
||||||
|
Some snags and todos
|
||||||
|
|
||||||
|
* [ ] The signer should take a startup param "--no-change", for UIs that do not contain the capability
|
||||||
|
to perform changes to things, only approve/deny. Such a UI should be able to start the signer in
|
||||||
|
a more secure mode by telling it that it only wants approve/deny capabilities.
|
||||||
|
|
||||||
|
* [x] It would be nice if the signer could collect new 4byte-id:s/method selectors, and have a
|
||||||
|
secondary database for those (`4byte_custom.json`). Users could then (optionally) submit their collections for
|
||||||
|
inclusion upstream.
|
||||||
|
|
||||||
|
* It should be possible to configure the signer to check if an account is indeed known to it, before
|
||||||
|
passing on to the UI. The reason it currently does not, is that it would make it possible to enumerate
|
||||||
|
accounts if it immediately returned "unknown account".
|
||||||
|
* [x] It should be possible to configure the signer to auto-allow listing (certain) accounts, instead of asking every time.
|
||||||
|
* [x] Done Upon startup, the signer should spit out some info to the caller (particularly important when executed in `stdio-ui`-mode),
|
||||||
|
invoking methods with the following info:
|
||||||
|
* [x] Version info about the signer
|
||||||
|
* [x] Address of API (http/ipc)
|
||||||
|
* [ ] List of known accounts
|
||||||
|
* [ ] Have a default timeout on signing operations, so that if the user has not answered withing e.g. 60 seconds, the request is rejected.
|
||||||
|
* [ ] `account_signRawTransaction`
|
||||||
|
* [ ] `account_bulkSignTransactions([] transactions)` should
|
||||||
|
* only exist if enabled via config/flag
|
||||||
|
* only allow non-data-sending transactions
|
||||||
|
* all txs must use the same `from`-account
|
||||||
|
* let the user confirm, showing
|
||||||
|
* the total amount
|
||||||
|
* the number of unique recipients
|
||||||
|
|
||||||
|
* Geth todos
|
||||||
|
- The signer should pass the `Origin` header as call-info to the UI. As of right now, the way that info about the request is
|
||||||
|
put together is a bit of a hack into the http server. This could probably be greatly improved
|
||||||
|
- Relay: Geth should be started in `geth --external_signer localhost:8550`.
|
||||||
|
- Currently, the Geth APIs use `common.Address` in the arguments to transaction submission (e.g `to` field). This
|
||||||
|
type is 20 `bytes`, and is incapable of carrying checksum information. The signer uses `common.MixedcaseAddress`, which
|
||||||
|
retains the original input.
|
||||||
|
- The Geth api should switch to use the same type, and relay `to`-account verbatim to the external api.
|
||||||
|
|
||||||
|
* [x] Storage
|
||||||
|
* [x] An encrypted key-value storage should be implemented
|
||||||
|
* See [rules.md](rules.md) for more info about this.
|
||||||
|
|
||||||
|
* Another potential thing to introduce is pairing.
|
||||||
|
* To prevent spurious requests which users just accept, implement a way to "pair" the caller with the signer (external API).
|
||||||
|
* Thus geth/mist/cpp would cryptographically handshake and afterwards the caller would be allowed to make signing requests.
|
||||||
|
* This feature would make the addition of rules less dangerous.
|
||||||
|
|
||||||
|
* Wallets / accounts. Add API methods for wallets.
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
### External API
|
||||||
|
|
||||||
|
The signer listens to HTTP requests on `rpcaddr`:`rpcport`, with the same JSONRPC standard as Geth. The messages are
|
||||||
|
expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
||||||
|
|
||||||
|
Some of these call can require user interaction. Clients must be aware that responses
|
||||||
|
may be delayed significanlty or may never be received if a users decides to ignore the confirmation request.
|
||||||
|
|
||||||
|
The External API is **untrusted** : it does not accept credentials over this api, nor does it expect
|
||||||
|
that requests have any authority.
|
||||||
|
|
||||||
|
### UI API
|
||||||
|
|
||||||
|
The signer has one native console-based UI, for operation without any standalone tools.
|
||||||
|
However, there is also an API to communicate with an external UI. To enable that UI,
|
||||||
|
the signer needs to be executed with the `--stdio-ui` option, which allocates the
|
||||||
|
`stdin`/`stdout` for the UI-api.
|
||||||
|
|
||||||
|
An example (insecure) proof-of-concept of has been implemented in `pythonsigner.py`.
|
||||||
|
|
||||||
|
The model is as follows:
|
||||||
|
|
||||||
|
* The user starts the UI app (`pythonsigner.py`).
|
||||||
|
* The UI app starts the `signer` with `--stdio-ui`, and listens to the
|
||||||
|
process output for confirmation-requests.
|
||||||
|
* The `signer` opens the external http api.
|
||||||
|
* When the `signer` receives requests, it sends a `jsonrpc` request via `stdout`.
|
||||||
|
* The UI app prompts the user accordingly, and responds to the `signer`
|
||||||
|
* The `signer` signs (or not), and responds to the original request.
|
||||||
|
|
||||||
|
## External API
|
||||||
|
|
||||||
|
See the [external api changelog](extapi_changelog.md) for information about changes to this API.
|
||||||
|
|
||||||
|
### Encoding
|
||||||
|
- number: positive integers that are hex encoded
|
||||||
|
- data: hex encoded data
|
||||||
|
- string: ASCII string
|
||||||
|
|
||||||
|
All hex encoded values must be prefixed with `0x`.
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### account_new
|
||||||
|
|
||||||
|
#### Create new password protected account
|
||||||
|
|
||||||
|
The signer will generate a new private key, encrypts it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and stores it in the keystore directory.
|
||||||
|
The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- address [string]: account address that is derived from the generated key
|
||||||
|
- url [string]: location of the keyfile
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_new",
|
||||||
|
"params": []
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133",
|
||||||
|
"url": "keystore:///my/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_list
|
||||||
|
|
||||||
|
#### List available accounts
|
||||||
|
List all accounts that this signer currently manages
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- array with account records:
|
||||||
|
- account.address [string]: account address that is derived from the generated key
|
||||||
|
- account.type [string]: type of the
|
||||||
|
- account.url [string]: location of the account
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_list"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": [
|
||||||
|
{
|
||||||
|
"address": "0xafb2f771f58513609765698f65d3f2f0224a956f",
|
||||||
|
"type": "account",
|
||||||
|
"url": "keystore:///tmp/keystore/UTC--2017-08-24T07-26-47.162109726Z--afb2f771f58513609765698f65d3f2f0224a956f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0xbea9183f8f4f03d427f6bcea17388bdff1cab133",
|
||||||
|
"type": "account",
|
||||||
|
"url": "keystore:///tmp/keystore/UTC--2017-08-24T08-40-15.419655028Z--bea9183f8f4f03d427f6bcea17388bdff1cab133"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_signTransaction
|
||||||
|
|
||||||
|
#### Sign transactions
|
||||||
|
Signs a transactions and responds with the signed transaction in RLP encoded form.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
2. transaction object:
|
||||||
|
- `from` [address]: account to send the transaction from
|
||||||
|
- `to` [address]: receiver account. If omitted or `0x`, will cause contract creation.
|
||||||
|
- `gas` [number]: maximum amount of gas to burn
|
||||||
|
- `gasPrice` [number]: gas price
|
||||||
|
- `value` [number:optional]: amount of Wei to send with the transaction
|
||||||
|
- `data` [data:optional]: input data
|
||||||
|
- `nonce` [number]: account nonce
|
||||||
|
3. method signature [string:optional]
|
||||||
|
- The method signature, if present, is to aid decoding the calldata. Should consist of `methodname(paramtype,...)`, e.g. `transfer(uint256,address)`. The signer may use this data to parse the supplied calldata, and show the user. The data, however, is considered totally untrusted, and reliability is not expected.
|
||||||
|
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- signed transaction in RLP encoded form [data]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_signTransaction",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"from": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
|
||||||
|
"gas": "0x55555",
|
||||||
|
"gasPrice": "0x1234",
|
||||||
|
"input": "0xabcd",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"value": "0x1234"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 67,
|
||||||
|
"error": {
|
||||||
|
"code": -32000,
|
||||||
|
"message": "Request denied"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### Sample call with ABI-data
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_signTransaction",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"from": "0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"value": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"
|
||||||
|
},
|
||||||
|
"safeSend(address)"
|
||||||
|
],
|
||||||
|
"id": 67
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 67,
|
||||||
|
"result": {
|
||||||
|
"raw": "0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||||
|
"tx": {
|
||||||
|
"nonce": "0x0",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"gas": "0x333",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"value": "0x0",
|
||||||
|
"input": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||||
|
"v": "0x26",
|
||||||
|
"r": "0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e",
|
||||||
|
"s": "0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663",
|
||||||
|
"hash": "0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bash example:
|
||||||
|
```bash
|
||||||
|
#curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
|
{"jsonrpc":"2.0","id":67,"result":{"raw":"0xf88380018203339407a565b7ed7d7a678680a4c162885bedbb695fe080a44401a6e4000000000000000000000000000000000000000000000000000000000000001226a0223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20ea02aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","tx":{"nonce":"0x0","gasPrice":"0x1","gas":"0x333","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0","value":"0x0","input":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012","v":"0x26","r":"0x223a7c9bcf5531c99be5ea7082183816eb20cfe0bbc322e97cc5c7f71ab8b20e","s":"0x2aadee6b34b45bb15bc42d9c09de4a6754e7000908da72d48cc7704971491663","hash":"0xeba2df809e7a612a0a0d444ccfa5c839624bdc00dd29e3340d46df3870f8a30e"}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### account_sign
|
||||||
|
|
||||||
|
#### Sign data
|
||||||
|
Signs a chunk of data and returns the calculated signature.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- account [address]: account to sign with
|
||||||
|
- data [data]: data to sign
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- calculated signature [data]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_sign",
|
||||||
|
"params": [
|
||||||
|
"0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db",
|
||||||
|
"0xaabbccdd"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": "0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_ecRecover
|
||||||
|
|
||||||
|
#### Recover address
|
||||||
|
Derive the address from the account that was used to sign data from the data and signature.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- data [data]: data that was signed
|
||||||
|
- signature [data]: the signature to verify
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- derived account [address]
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_ecRecover",
|
||||||
|
"params": [
|
||||||
|
"0xaabbccdd",
|
||||||
|
"0x5b6693f153b48ec1c706ba4169960386dbaa6903e249cc79a8e6ddc434451d417e1e57327872c7f538beeb323c300afa9999a3d4a5de6caf3be0d5ef832b67ef1c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": "0x1923f626bb8dc025849e00f99c25fe2b2f7fb0db"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_import
|
||||||
|
|
||||||
|
#### Import account
|
||||||
|
Import a private key into the keystore. The imported key is expected to be encrypted according to the web3 keystore
|
||||||
|
format.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- account [object]: key in [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) (retrieved with account_export)
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- imported key [object]:
|
||||||
|
- key.address [address]: address of the imported key
|
||||||
|
- key.type [string]: type of the account
|
||||||
|
- key.url [string]: key URL
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_import",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "c7412fc59930fd90099c917a50e5f11d0934b2f5",
|
||||||
|
"crypto": {
|
||||||
|
"cipher": "aes-128-ctr",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "401c39a7c7af0388491c3d3ecb39f532"
|
||||||
|
},
|
||||||
|
"ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204",
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a"
|
||||||
|
},
|
||||||
|
"mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806"
|
||||||
|
},
|
||||||
|
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"address": "0xc7412fc59930fd90099c917a50e5f11d0934b2f5",
|
||||||
|
"type": "account",
|
||||||
|
"url": "keystore:///tmp/keystore/UTC--2017-08-24T11-00-42.032024108Z--c7412fc59930fd90099c917a50e5f11d0934b2f5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### account_export
|
||||||
|
|
||||||
|
#### Export account from keystore
|
||||||
|
Export a private key from the keystore. The exported private key is encrypted with the original passphrase. When the
|
||||||
|
key is imported later this passphrase is required.
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
- account [address]: export private key that is associated with this account
|
||||||
|
|
||||||
|
#### Result
|
||||||
|
- exported key, see [web3 keystore format](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) for
|
||||||
|
more information
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "account_export",
|
||||||
|
"params": [
|
||||||
|
"0xc7412fc59930fd90099c917a50e5f11d0934b2f5"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"address": "c7412fc59930fd90099c917a50e5f11d0934b2f5",
|
||||||
|
"crypto": {
|
||||||
|
"cipher": "aes-128-ctr",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "401c39a7c7af0388491c3d3ecb39f532"
|
||||||
|
},
|
||||||
|
"ciphertext": "eb045260b18dd35cd0e6d99ead52f8fa1e63a6b0af2d52a8de198e59ad783204",
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "9a657e3618527c9b5580ded60c12092e5038922667b7b76b906496f021bb841a"
|
||||||
|
},
|
||||||
|
"mac": "880dc10bc06e9cec78eb9830aeb1e7a4a26b4c2c19615c94acb632992b952806"
|
||||||
|
},
|
||||||
|
"id": "09bccb61-b8d3-4e93-bf4f-205a8194f0b9",
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## UI API
|
||||||
|
|
||||||
|
These methods needs to be implemented by a UI listener.
|
||||||
|
|
||||||
|
By starting the signer with the switch `--stdio-ui-test`, the signer will invoke all known methods, and expect the UI to respond with
|
||||||
|
denials. This can be used during development to ensure that the API is (at least somewhat) correctly implemented.
|
||||||
|
See `pythonsigner`, which can be invoked via `python3 pythonsigner.py test` to perform the 'denial-handshake-test'.
|
||||||
|
|
||||||
|
All methods in this API uses object-based parameters, so that there can be no mixups of parameters: each piece of data is accessed by key.
|
||||||
|
|
||||||
|
See the [ui api changelog](intapi_changelog.md) for information about changes to this API.
|
||||||
|
|
||||||
|
OBS! A slight deviation from `json` standard is in place: every request and response should be confined to a single line.
|
||||||
|
Whereas the `json` specification allows for linebreaks, linebreaks __should not__ be used in this communication channel, to make
|
||||||
|
things simpler for both parties.
|
||||||
|
|
||||||
|
### ApproveTx
|
||||||
|
|
||||||
|
Invoked when there's a transaction for approval.
|
||||||
|
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
Here's a method invocation:
|
||||||
|
```bash
|
||||||
|
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||||
|
"to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Invalid checksum on to-address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Info",
|
||||||
|
"message": "safeSend(address: 0x0000000000000000000000000000000000000012)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "127.0.0.1:48486",
|
||||||
|
"local": "localhost:8550",
|
||||||
|
"scheme": "HTTP/1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The same method invocation, but with invalid data:
|
||||||
|
```bash
|
||||||
|
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x694267f14675d7e1b9494fd8d72fefe1755710fa","gas":"0x333","gasPrice":"0x1","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x0", "data":"0x4401a6e40000000000000002000000000000000000000000000000000000000000000012"},"safeSend(address)"],"id":67}' http://localhost:8550/
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0x0x694267f14675d7e1b9494fd8d72fefe1755710fa",
|
||||||
|
"to": "0x0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x1",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000002000000000000000000000000000000000000000000000012",
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Invalid checksum on to-address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Transaction data did not match ABI-interface: WARNING: Supplied data is stuffed with extra data. \nWant 0000000000000002000000000000000000000000000000000000000000000012\nHave 0000000000000000000000000000000000000000000000000000000000000012\nfor method safeSend(address)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "127.0.0.1:48492",
|
||||||
|
"local": "localhost:8550",
|
||||||
|
"scheme": "HTTP/1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
One which has missing `to`, but with no `data`:
|
||||||
|
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 3,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "",
|
||||||
|
"to": null,
|
||||||
|
"gas": "0x0",
|
||||||
|
"gasPrice": "0x0",
|
||||||
|
"value": "0x0",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": null,
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "CRITICAL",
|
||||||
|
"message": "Tx will create contract with empty code!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ApproveExport
|
||||||
|
|
||||||
|
Invoked when a request to export an account has been made.
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 7,
|
||||||
|
"method": "ApproveExport",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "0x0000000000000000000000000000000000000000",
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ApproveListing
|
||||||
|
|
||||||
|
Invoked when a request for account listing has been made.
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"method": "ApproveListing",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"type": "Account",
|
||||||
|
"url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-20T14-44-54.089682944Z--123409812340981234098123409812deadbeef42",
|
||||||
|
"address": "0x123409812340981234098123409812deadbeef42"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Account",
|
||||||
|
"url": "keystore:///home/bazonk/.ethereum/keystore/UTC--2017-11-23T21-59-03.199240693Z--cafebabedeadbeef34098123409812deadbeef42",
|
||||||
|
"address": "0xcafebabedeadbeef34098123409812deadbeef42"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### ApproveSignData
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 4,
|
||||||
|
"method": "ApproveSignData",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"address": "0x123409812340981234098123409812deadbeef42",
|
||||||
|
"raw_data": "0x01020304",
|
||||||
|
"message": "\u0019Ethereum Signed Message:\n4\u0001\u0002\u0003\u0004",
|
||||||
|
"hash": "0x7e3a4e7a9d1744bc5c675c25e1234ca8ed9162bd17f78b9085e48047c15ac310",
|
||||||
|
"meta": {
|
||||||
|
"remote": "signer binary",
|
||||||
|
"local": "main",
|
||||||
|
"scheme": "in-proc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ShowInfo
|
||||||
|
|
||||||
|
The UI should show the info to the user. Does not expect response.
|
||||||
|
|
||||||
|
#### Sample call
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 9,
|
||||||
|
"method": "ShowInfo",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"text": "Tests completed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### ShowError
|
||||||
|
|
||||||
|
The UI should show the info to the user. Does not expect response.
|
||||||
|
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "ShowError",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"text": "Testing 'ShowError'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### OnApproved
|
||||||
|
|
||||||
|
`OnApprovedTx` is called when a transaction has been approved and signed. The call contains the return value that will be sent to the external caller. The return value from this method is ignored - the reason for having this callback is to allow the ruleset to keep track of approved transactions.
|
||||||
|
|
||||||
|
When implementing rate-limited rules, this callback should be used.
|
||||||
|
|
||||||
|
TLDR; Use this method to keep track of signed transactions, instead of using the data in `ApproveTx`.
|
||||||
|
|
||||||
|
### OnSignerStartup
|
||||||
|
|
||||||
|
This method provide the UI with information about what API version the signer uses (both internal and external) aswell as build-info and external api,
|
||||||
|
in k/v-form.
|
||||||
|
|
||||||
|
Example call:
|
||||||
|
```json
|
||||||
|
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "OnSignerStartup",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"extapi_http": "http://localhost:8550",
|
||||||
|
"extapi_ipc": null,
|
||||||
|
"extapi_version": "2.0.0",
|
||||||
|
"intapi_version": "1.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Rules for UI apis
|
||||||
|
|
||||||
|
A UI should conform to the following rules.
|
||||||
|
|
||||||
|
* A UI MUST NOT load any external resources that were not embedded/part of the UI package.
|
||||||
|
* For example, not load icons, stylesheets from the internet
|
||||||
|
* Not load files from the filesystem, unless they reside in the same local directory (e.g. config files)
|
||||||
|
* A Graphical UI MUST show the blocky-identicon for ethereum addresses.
|
||||||
|
* A UI MUST warn display approproate warning if the destination-account is formatted with invalid checksum.
|
||||||
|
* A UI MUST NOT open any ports or services
|
||||||
|
* The signer opens the public port
|
||||||
|
* A UI SHOULD verify the permissions on the signer binary, and refuse to execute or warn if permissions allow non-user write.
|
||||||
|
* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed
|
||||||
|
* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts
|
||||||
|
* The signer provides accounts
|
||||||
|
* A UI SHOULD, to the best extent possible, use static linking / bundling, so that requried libraries are bundled
|
||||||
|
along with the UI.
|
||||||
|
|
||||||
|
|
||||||
|
### UI Implementations
|
||||||
|
|
||||||
|
There are a couple of implementation for a UI. We'll try to keep this list up to date.
|
||||||
|
|
||||||
|
| Name | Repo | UI type| No external resources| Blocky support| Verifies permissions | Hash information | No secondary storage | Statically linked| Can modify parameters|
|
||||||
|
| ---- | ---- | -------| ---- | ---- | ---- |---- | ---- | ---- | ---- |
|
||||||
|
| QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
||||||
|
| GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
||||||
|
| Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
||||||
BIN
cmd/clef/docs/qubes/clef_qubes_http.png
Normal file
BIN
cmd/clef/docs/qubes/clef_qubes_http.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
cmd/clef/docs/qubes/clef_qubes_qrexec.png
Normal file
BIN
cmd/clef/docs/qubes/clef_qubes_qrexec.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
cmd/clef/docs/qubes/qrexec-example.png
Normal file
BIN
cmd/clef/docs/qubes/qrexec-example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
23
cmd/clef/docs/qubes/qubes-client.py
Normal file
23
cmd/clef/docs/qubes/qubes-client.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""
|
||||||
|
This implements a dispatcher which listens to localhost:8550, and proxies
|
||||||
|
requests via qrexec to the service qubes.EthSign on a target domain
|
||||||
|
"""
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import socketserver,subprocess
|
||||||
|
|
||||||
|
PORT=8550
|
||||||
|
TARGET_DOMAIN= 'debian-work'
|
||||||
|
|
||||||
|
class Dispatcher(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
post_data = self.rfile.read(int(self.headers['Content-Length']))
|
||||||
|
p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
output = p.communicate(post_data)[0]
|
||||||
|
self.wfile.write(output)
|
||||||
|
|
||||||
|
|
||||||
|
with socketserver.TCPServer(("",PORT), Dispatcher) as httpd:
|
||||||
|
print("Serving at port", PORT)
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
16
cmd/clef/docs/qubes/qubes.Clefsign
Normal file
16
cmd/clef/docs/qubes/qubes.Clefsign
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SIGNER_BIN="/home/user/tools/clef/clef"
|
||||||
|
SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN"
|
||||||
|
|
||||||
|
# Start clef if not already started
|
||||||
|
if [ ! -S /home/user/.clef/clef.ipc ]; then
|
||||||
|
$SIGNER_CMD &
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Should be started by now
|
||||||
|
if [ -S /home/user/.clef/clef.ipc ]; then
|
||||||
|
# Post incoming request to HTTP channel
|
||||||
|
curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null
|
||||||
|
fi
|
||||||
BIN
cmd/clef/docs/qubes/qubes_newaccount-1.png
Normal file
BIN
cmd/clef/docs/qubes/qubes_newaccount-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
cmd/clef/docs/qubes/qubes_newaccount-2.png
Normal file
BIN
cmd/clef/docs/qubes/qubes_newaccount-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
198
cmd/clef/docs/setup.md
Normal file
198
cmd/clef/docs/setup.md
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
# Setting up Clef
|
||||||
|
|
||||||
|
This document describes how Clef can be used in a more secure manner than executing it from your everyday laptop,
|
||||||
|
in order to ensure that the keys remain safe in the event that your computer should get compromised.
|
||||||
|
|
||||||
|
## Qubes OS
|
||||||
|
|
||||||
|
|
||||||
|
### Background
|
||||||
|
|
||||||
|
The Qubes operating system is based around virtual machines (qubes), where a set of virtual machines are configured, typically for
|
||||||
|
different purposes such as:
|
||||||
|
|
||||||
|
- personal
|
||||||
|
- Your personal email, browsing etc
|
||||||
|
- work
|
||||||
|
- Work email etc
|
||||||
|
- vault
|
||||||
|
- a VM without network access, where gpg-keys and/or keepass credentials are stored.
|
||||||
|
|
||||||
|
A couple of dedicated virtual machines handle externalities:
|
||||||
|
|
||||||
|
- sys-net provides networking to all other (network-enabled) machines
|
||||||
|
- sys-firewall handles firewall rules
|
||||||
|
- sys-usb handles USB devices, and can map usb-devices to certain qubes.
|
||||||
|
|
||||||
|
The goal of this document is to describe how we can set up clef to provide secure transaction
|
||||||
|
signing from a `vault` vm, to another networked qube which runs Dapps.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
There are two ways that this can be achieved: integrated via Qubes or integrated via networking.
|
||||||
|
|
||||||
|
|
||||||
|
#### 1. Qubes Integrated
|
||||||
|
|
||||||
|
Qubes provdes a facility for inter-qubes communication via `qrexec`. A qube can request to make a cross-qube RPC request
|
||||||
|
to another qube. The OS then asks the user if the call is permitted.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
A policy-file can be created to allow such interaction. On the `target` domain, a service is invoked which can read the
|
||||||
|
`stdin` from the `client` qube.
|
||||||
|
|
||||||
|
This is how [Split GPG](https://www.qubes-os.org/doc/split-gpg/) is implemented. We can set up Clef the same way:
|
||||||
|
|
||||||
|
##### Server
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
On the `target` qubes, we need to define the rpc service.
|
||||||
|
|
||||||
|
[qubes.Clefsign](qubes/qubes.Clefsign):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SIGNER_BIN="/home/user/tools/clef/clef"
|
||||||
|
SIGNER_CMD="/home/user/tools/gtksigner/gtkui.py -s $SIGNER_BIN"
|
||||||
|
|
||||||
|
# Start clef if not already started
|
||||||
|
if [ ! -S /home/user/.clef/clef.ipc ]; then
|
||||||
|
$SIGNER_CMD &
|
||||||
|
sleep 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Should be started by now
|
||||||
|
if [ -S /home/user/.clef/clef.ipc ]; then
|
||||||
|
# Post incoming request to HTTP channel
|
||||||
|
curl -H "Content-Type: application/json" -X POST -d @- http://localhost:8550 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
```
|
||||||
|
This RPC service is not complete (see notes about HTTP headers below), but works as a proof-of-concept.
|
||||||
|
It will forward the data received on `stdin` (forwarded by the OS) to Clef's HTTP channel.
|
||||||
|
|
||||||
|
It would have been possible to send data directly to the `/home/user/.clef/.clef.ipc`
|
||||||
|
socket via e.g `nc -U /home/user/.clef/clef.ipc`, but the reason for sending the request
|
||||||
|
data over `HTTP` instead of `IPC` is that we want the ability to forward `HTTP` headers.
|
||||||
|
|
||||||
|
To enable the service:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
sudo cp qubes.Clefsign /etc/qubes-rpc/
|
||||||
|
sudo chmod +x /etc/qubes-rpc/ qubes.Clefsign
|
||||||
|
```
|
||||||
|
|
||||||
|
This setup uses [gtksigner](https://github.com/holiman/gtksigner), which is a very minimal GTK-based UI that works well
|
||||||
|
with minimal requirements.
|
||||||
|
|
||||||
|
##### Client
|
||||||
|
|
||||||
|
|
||||||
|
On the `client` qube, we need to create a listener which will receive the request from the Dapp, and proxy it.
|
||||||
|
|
||||||
|
|
||||||
|
[qubes-client.py](qubes/client/qubes-client.py):
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
"""
|
||||||
|
This implements a dispatcher which listens to localhost:8550, and proxies
|
||||||
|
requests via qrexec to the service qubes.EthSign on a target domain
|
||||||
|
"""
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import socketserver,subprocess
|
||||||
|
|
||||||
|
PORT=8550
|
||||||
|
TARGET_DOMAIN= 'debian-work'
|
||||||
|
|
||||||
|
class Dispatcher(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
post_data = self.rfile.read(int(self.headers['Content-Length']))
|
||||||
|
p = subprocess.Popen(['/usr/bin/qrexec-client-vm',TARGET_DOMAIN,'qubes.Clefsign'],stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
output = p.communicate(post_data)[0]
|
||||||
|
self.wfile.write(output)
|
||||||
|
|
||||||
|
|
||||||
|
with socketserver.TCPServer(("",PORT), Dispatcher) as httpd:
|
||||||
|
print("Serving at port", PORT)
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Testing
|
||||||
|
|
||||||
|
To test the flow, if we have set up `debian-work` as the `target`, we can do
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cat newaccnt.json
|
||||||
|
{ "id": 0, "jsonrpc": "2.0","method": "account_new","params": []}
|
||||||
|
|
||||||
|
$ cat newaccnt.json| qrexec-client-vm debian-work qubes.Clefsign
|
||||||
|
```
|
||||||
|
|
||||||
|
This should pop up first a dialog to allow the IPC call:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Followed by a GTK-dialog to approve the operation
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
To test the full flow, we use the client wrapper. Start it on the `client` qube:
|
||||||
|
```
|
||||||
|
[user@work qubes]$ python3 qubes-client.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the request over http (`client` qube):
|
||||||
|
```
|
||||||
|
[user@work clef]$ cat newaccnt.json | curl -X POST -d @- http://localhost:8550
|
||||||
|
```
|
||||||
|
And it should show the same popups again.
|
||||||
|
|
||||||
|
##### Pros and cons
|
||||||
|
|
||||||
|
The benefits of this setup are:
|
||||||
|
|
||||||
|
- This is the qubes-os intended model for inter-qube communication,
|
||||||
|
- and thus benefits from qubes-os dialogs and policies for user approval
|
||||||
|
|
||||||
|
However, it comes with a couple of drawbacks:
|
||||||
|
|
||||||
|
- The `qubes-gpg-client` must forward the http request via RPC to the `target` qube. When doing so, the proxy
|
||||||
|
will either drop important headers, or replace them.
|
||||||
|
- The `Host` header is most likely `localhost`
|
||||||
|
- The `Origin` header must be forwarded
|
||||||
|
- Information about the remote ip must be added as a `X-Forwarded-For`. However, Clef cannot always trust an `XFF` header,
|
||||||
|
since malicious clients may lie about `XFF` in order to fool the http server into believing it comes from another address.
|
||||||
|
- Even with a policy in place to allow rpc-calls between `caller` and `target`, there will be several popups:
|
||||||
|
- One qubes-specific where the user specifies the `target` vm
|
||||||
|
- One clef-specific to approve the transaction
|
||||||
|
|
||||||
|
|
||||||
|
#### 2. Network integrated
|
||||||
|
|
||||||
|
The second way to set up Clef on a qubes system is to allow networking, and have Clef listen to a port which is accessible
|
||||||
|
form other qubes.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## USBArmory
|
||||||
|
|
||||||
|
The [USB armory](https://inversepath.com/usbarmory) is an open source hardware design with an 800 Mhz ARM processor. It is a pocket-size
|
||||||
|
computer. When inserted into a laptop, it identifies itself as a USB network interface, basically adding another network
|
||||||
|
to your computer. Over this new network interface, you can SSH into the device.
|
||||||
|
|
||||||
|
Running Clef off a USB armory means that you can use the armory as a very versatile offline computer, which only
|
||||||
|
ever connects to a local network between your computer and the device itself.
|
||||||
|
|
||||||
|
Needless to say, the while this model should be fairly secure against remote attacks, an attacker with physical access
|
||||||
|
to the USB Armory would trivially be able to extract the contents of the device filesystem.
|
||||||
|
|
||||||
25
cmd/clef/extapi_changelog.md
Normal file
25
cmd/clef/extapi_changelog.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
### Changelog for external API
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 2.0.0
|
||||||
|
|
||||||
|
* Commit `73abaf04b1372fa4c43201fb1b8019fe6b0a6f8d`, move `from` into `transaction` object in `signTransaction`. This
|
||||||
|
makes the `accounts_signTransaction` identical to the old `eth_signTransaction`.
|
||||||
|
|
||||||
|
|
||||||
|
#### 1.0.0
|
||||||
|
|
||||||
|
Initial release.
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
The API uses [semantic versioning](https://semver.org/).
|
||||||
|
|
||||||
|
TLDR; Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||||
|
|
||||||
|
* MAJOR version when you make incompatible API changes,
|
||||||
|
* MINOR version when you add functionality in a backwards-compatible manner, and
|
||||||
|
* PATCH version when you make backwards-compatible bug fixes.
|
||||||
|
|
||||||
|
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
|
||||||
86
cmd/clef/intapi_changelog.md
Normal file
86
cmd/clef/intapi_changelog.md
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
### Changelog for internal API (ui-api)
|
||||||
|
|
||||||
|
### 2.0.0
|
||||||
|
|
||||||
|
* Modify how `call_info` on a transaction is conveyed. New format:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"transaction": {
|
||||||
|
"from": "0x82A2A876D39022B3019932D30Cd9c97ad5616813",
|
||||||
|
"to": "0x07a565b7ed7d7a678680a4c162885bedbb695fe0",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x123",
|
||||||
|
"value": "0x10",
|
||||||
|
"nonce": "0x0",
|
||||||
|
"data": "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012",
|
||||||
|
"input": null
|
||||||
|
},
|
||||||
|
"call_info": [
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Invalid checksum on to-address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "WARNING",
|
||||||
|
"message": "Tx contains data, but provided ABI signature could not be matched: Did not match: test (0 matches)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"remote": "127.0.0.1:54286",
|
||||||
|
"local": "localhost:8550",
|
||||||
|
"scheme": "HTTP/1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2.0
|
||||||
|
|
||||||
|
* Add `OnStartup` method, to provide the UI with information about what API version
|
||||||
|
the signer uses (both internal and external) aswell as build-info and external api.
|
||||||
|
|
||||||
|
Example call:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "OnSignerStartup",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"extapi_http": "http://localhost:8550",
|
||||||
|
"extapi_ipc": null,
|
||||||
|
"extapi_version": "2.0.0",
|
||||||
|
"intapi_version": "1.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.1.0
|
||||||
|
|
||||||
|
* Add `OnApproved` method
|
||||||
|
|
||||||
|
#### 1.0.0
|
||||||
|
|
||||||
|
Initial release.
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
The API uses [semantic versioning](https://semver.org/).
|
||||||
|
|
||||||
|
TLDR; Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||||
|
|
||||||
|
* MAJOR version when you make incompatible API changes,
|
||||||
|
* MINOR version when you add functionality in a backwards-compatible manner, and
|
||||||
|
* PATCH version when you make backwards-compatible bug fixes.
|
||||||
|
|
||||||
|
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.
|
||||||
640
cmd/clef/main.go
Normal file
640
cmd/clef/main.go
Normal file
|
|
@ -0,0 +1,640 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// signer is a utility that can be used so sign transactions and
|
||||||
|
// arbitrary data.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/rules"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/storage"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExternalAPIVersion -- see extapi_changelog.md
|
||||||
|
const ExternalAPIVersion = "2.0.0"
|
||||||
|
|
||||||
|
// InternalAPIVersion -- see intapi_changelog.md
|
||||||
|
const InternalAPIVersion = "2.0.0"
|
||||||
|
|
||||||
|
const legalWarning = `
|
||||||
|
WARNING!
|
||||||
|
|
||||||
|
Clef is alpha software, and not yet publically released. This software has _not_ been audited, and there
|
||||||
|
are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software
|
||||||
|
unless you agree to take full responsibility for doing so, and know what you are doing.
|
||||||
|
|
||||||
|
TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE!
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
var (
|
||||||
|
logLevelFlag = cli.IntFlag{
|
||||||
|
Name: "loglevel",
|
||||||
|
Value: 4,
|
||||||
|
Usage: "log level to emit to the screen",
|
||||||
|
}
|
||||||
|
keystoreFlag = cli.StringFlag{
|
||||||
|
Name: "keystore",
|
||||||
|
Value: filepath.Join(node.DefaultDataDir(), "keystore"),
|
||||||
|
Usage: "Directory for the keystore",
|
||||||
|
}
|
||||||
|
configdirFlag = cli.StringFlag{
|
||||||
|
Name: "configdir",
|
||||||
|
Value: DefaultConfigDir(),
|
||||||
|
Usage: "Directory for Clef configuration",
|
||||||
|
}
|
||||||
|
rpcPortFlag = cli.IntFlag{
|
||||||
|
Name: "rpcport",
|
||||||
|
Usage: "HTTP-RPC server listening port",
|
||||||
|
Value: node.DefaultHTTPPort + 5,
|
||||||
|
}
|
||||||
|
signerSecretFlag = cli.StringFlag{
|
||||||
|
Name: "signersecret",
|
||||||
|
Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash",
|
||||||
|
}
|
||||||
|
dBFlag = cli.StringFlag{
|
||||||
|
Name: "4bytedb",
|
||||||
|
Usage: "File containing 4byte-identifiers",
|
||||||
|
Value: "./4byte.json",
|
||||||
|
}
|
||||||
|
customDBFlag = cli.StringFlag{
|
||||||
|
Name: "4bytedb-custom",
|
||||||
|
Usage: "File used for writing new 4byte-identifiers submitted via API",
|
||||||
|
Value: "./4byte-custom.json",
|
||||||
|
}
|
||||||
|
auditLogFlag = cli.StringFlag{
|
||||||
|
Name: "auditlog",
|
||||||
|
Usage: "File used to emit audit logs. Set to \"\" to disable",
|
||||||
|
Value: "audit.log",
|
||||||
|
}
|
||||||
|
ruleFlag = cli.StringFlag{
|
||||||
|
Name: "rules",
|
||||||
|
Usage: "Enable rule-engine",
|
||||||
|
Value: "rules.json",
|
||||||
|
}
|
||||||
|
stdiouiFlag = cli.BoolFlag{
|
||||||
|
Name: "stdio-ui",
|
||||||
|
Usage: "Use STDIN/STDOUT as a channel for an external UI. " +
|
||||||
|
"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
|
||||||
|
"interface, and can be used when Clef is started by an external process.",
|
||||||
|
}
|
||||||
|
testFlag = cli.BoolFlag{
|
||||||
|
Name: "stdio-ui-test",
|
||||||
|
Usage: "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'.",
|
||||||
|
}
|
||||||
|
app = cli.NewApp()
|
||||||
|
initCommand = cli.Command{
|
||||||
|
Action: utils.MigrateFlags(initializeSecrets),
|
||||||
|
Name: "init",
|
||||||
|
Usage: "Initialize the signer, generate secret storage",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
logLevelFlag,
|
||||||
|
configdirFlag,
|
||||||
|
},
|
||||||
|
Description: `
|
||||||
|
The init command generates a master seed which Clef can use to store credentials and data needed for
|
||||||
|
the rule-engine to work.`,
|
||||||
|
}
|
||||||
|
attestCommand = cli.Command{
|
||||||
|
Action: utils.MigrateFlags(attestFile),
|
||||||
|
Name: "attest",
|
||||||
|
Usage: "Attest that a js-file is to be used",
|
||||||
|
ArgsUsage: "<sha256sum>",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
logLevelFlag,
|
||||||
|
configdirFlag,
|
||||||
|
signerSecretFlag,
|
||||||
|
},
|
||||||
|
Description: `
|
||||||
|
The attest command stores the sha256 of the rule.js-file that you want to use for automatic processing of
|
||||||
|
incoming requests.
|
||||||
|
|
||||||
|
Whenever you make an edit to the rule file, you need to use attestation to tell
|
||||||
|
Clef that the file is 'safe' to execute.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
addCredentialCommand = cli.Command{
|
||||||
|
Action: utils.MigrateFlags(addCredential),
|
||||||
|
Name: "addpw",
|
||||||
|
Usage: "Store a credential for a keystore file",
|
||||||
|
ArgsUsage: "<address> <password>",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
logLevelFlag,
|
||||||
|
configdirFlag,
|
||||||
|
signerSecretFlag,
|
||||||
|
},
|
||||||
|
Description: `
|
||||||
|
The addpw command stores a password for a given address (keyfile). If you invoke it with only one parameter, it will
|
||||||
|
remove any stored credential for that address (keyfile)
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
app.Name = "Clef"
|
||||||
|
app.Usage = "Manage Ethereum account operations"
|
||||||
|
app.Flags = []cli.Flag{
|
||||||
|
logLevelFlag,
|
||||||
|
keystoreFlag,
|
||||||
|
configdirFlag,
|
||||||
|
utils.NetworkIdFlag,
|
||||||
|
utils.LightKDFFlag,
|
||||||
|
utils.NoUSBFlag,
|
||||||
|
utils.RPCListenAddrFlag,
|
||||||
|
utils.RPCVirtualHostsFlag,
|
||||||
|
utils.IPCDisabledFlag,
|
||||||
|
utils.IPCPathFlag,
|
||||||
|
utils.RPCEnabledFlag,
|
||||||
|
rpcPortFlag,
|
||||||
|
signerSecretFlag,
|
||||||
|
dBFlag,
|
||||||
|
customDBFlag,
|
||||||
|
auditLogFlag,
|
||||||
|
ruleFlag,
|
||||||
|
stdiouiFlag,
|
||||||
|
testFlag,
|
||||||
|
}
|
||||||
|
app.Action = signer
|
||||||
|
app.Commands = []cli.Command{initCommand, attestCommand, addCredentialCommand}
|
||||||
|
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeSecrets(c *cli.Context) error {
|
||||||
|
if err := initialize(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
configDir := c.String(configdirFlag.Name)
|
||||||
|
|
||||||
|
masterSeed := make([]byte, 256)
|
||||||
|
n, err := io.ReadFull(rand.Reader, masterSeed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != len(masterSeed) {
|
||||||
|
return fmt.Errorf("failed to read enough random")
|
||||||
|
}
|
||||||
|
err = os.Mkdir(configDir, 0700)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
location := filepath.Join(configDir, "secrets.dat")
|
||||||
|
if _, err := os.Stat(location); err == nil {
|
||||||
|
return fmt.Errorf("file %v already exists, will not overwrite", location)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(location, masterSeed, 0700)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("A master seed has been generated into %s\n", location)
|
||||||
|
fmt.Printf(`
|
||||||
|
This is required to be able to store credentials, such as :
|
||||||
|
* Passwords for keystores (used by rule engine)
|
||||||
|
* Storage for javascript rules
|
||||||
|
* Hash of rule-file
|
||||||
|
|
||||||
|
You should treat that file with utmost secrecy, and make a backup of it.
|
||||||
|
NOTE: This file does not contain your accounts. Those need to be backed up separately!
|
||||||
|
|
||||||
|
`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func attestFile(ctx *cli.Context) error {
|
||||||
|
if len(ctx.Args()) < 1 {
|
||||||
|
utils.Fatalf("This command requires an argument.")
|
||||||
|
}
|
||||||
|
if err := initialize(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stretchedKey, err := readMasterKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
configDir := ctx.String(configdirFlag.Name)
|
||||||
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
||||||
|
confKey := crypto.Keccak256([]byte("config"), stretchedKey)
|
||||||
|
|
||||||
|
// Initialize the encrypted storages
|
||||||
|
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confKey)
|
||||||
|
val := ctx.Args().First()
|
||||||
|
configStorage.Put("ruleset_sha256", val)
|
||||||
|
log.Info("Ruleset attestation updated", "sha256", val)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCredential(ctx *cli.Context) error {
|
||||||
|
if len(ctx.Args()) < 1 {
|
||||||
|
utils.Fatalf("This command requires at leaste one argument.")
|
||||||
|
}
|
||||||
|
if err := initialize(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stretchedKey, err := readMasterKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
configDir := ctx.String(configdirFlag.Name)
|
||||||
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
||||||
|
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
|
||||||
|
|
||||||
|
// Initialize the encrypted storages
|
||||||
|
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
|
||||||
|
key := ctx.Args().First()
|
||||||
|
value := ""
|
||||||
|
if len(ctx.Args()) > 1 {
|
||||||
|
value = ctx.Args().Get(1)
|
||||||
|
}
|
||||||
|
pwStorage.Put(key, value)
|
||||||
|
log.Info("Credential store updated", "key", key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialize(c *cli.Context) error {
|
||||||
|
// Set up the logger to print everything
|
||||||
|
logOutput := os.Stdout
|
||||||
|
if c.Bool(stdiouiFlag.Name) {
|
||||||
|
logOutput = os.Stderr
|
||||||
|
// If using the stdioui, we can't do the 'confirm'-flow
|
||||||
|
fmt.Fprintf(logOutput, legalWarning)
|
||||||
|
} else {
|
||||||
|
if !confirm(legalWarning) {
|
||||||
|
return fmt.Errorf("aborted by user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(logOutput, log.TerminalFormat(true))))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func signer(c *cli.Context) error {
|
||||||
|
if err := initialize(c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
ui core.SignerUI
|
||||||
|
)
|
||||||
|
if c.Bool(stdiouiFlag.Name) {
|
||||||
|
log.Info("Using stdin/stdout as UI-channel")
|
||||||
|
ui = core.NewStdIOUI()
|
||||||
|
} else {
|
||||||
|
log.Info("Using CLI as UI-channel")
|
||||||
|
ui = core.NewCommandlineUI()
|
||||||
|
}
|
||||||
|
db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
|
||||||
|
|
||||||
|
var (
|
||||||
|
api core.ExternalAPI
|
||||||
|
)
|
||||||
|
|
||||||
|
configDir := c.String(configdirFlag.Name)
|
||||||
|
if stretchedKey, err := readMasterKey(c); err != nil {
|
||||||
|
log.Info("No master seed provided, rules disabled")
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
||||||
|
|
||||||
|
// Generate domain specific keys
|
||||||
|
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
|
||||||
|
jskey := crypto.Keccak256([]byte("jsstorage"), stretchedKey)
|
||||||
|
confkey := crypto.Keccak256([]byte("config"), stretchedKey)
|
||||||
|
|
||||||
|
// Initialize the encrypted storages
|
||||||
|
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
|
||||||
|
jsStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "jsstorage.json"), jskey)
|
||||||
|
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
|
||||||
|
|
||||||
|
//Do we have a rule-file?
|
||||||
|
ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name))
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
|
||||||
|
} else {
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write(ruleJS)
|
||||||
|
shasum := hasher.Sum(nil)
|
||||||
|
storedShasum := configStorage.Get("ruleset_sha256")
|
||||||
|
if storedShasum != hex.EncodeToString(shasum) {
|
||||||
|
log.Info("Could not validate ruleset hash, rules not enabled", "got", hex.EncodeToString(shasum), "expected", storedShasum)
|
||||||
|
} else {
|
||||||
|
// Initialize rules
|
||||||
|
ruleEngine, err := rules.NewRuleEvaluator(ui, jsStorage, pwStorage)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
ruleEngine.Init(string(ruleJS))
|
||||||
|
ui = ruleEngine
|
||||||
|
log.Info("Rule engine configured", "file", c.String(ruleFlag.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiImpl := core.NewSignerAPI(
|
||||||
|
c.Int64(utils.NetworkIdFlag.Name),
|
||||||
|
c.String(keystoreFlag.Name),
|
||||||
|
c.Bool(utils.NoUSBFlag.Name),
|
||||||
|
ui, db,
|
||||||
|
c.Bool(utils.LightKDFFlag.Name))
|
||||||
|
|
||||||
|
api = apiImpl
|
||||||
|
|
||||||
|
// Audit logging
|
||||||
|
if logfile := c.String(auditLogFlag.Name); logfile != "" {
|
||||||
|
api, err = core.NewAuditLogger(logfile, api)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
log.Info("Audit logs configured", "file", logfile)
|
||||||
|
}
|
||||||
|
// register signer API with server
|
||||||
|
var (
|
||||||
|
extapiURL = "n/a"
|
||||||
|
ipcapiURL = "n/a"
|
||||||
|
)
|
||||||
|
rpcAPI := []rpc.API{
|
||||||
|
{
|
||||||
|
Namespace: "account",
|
||||||
|
Public: true,
|
||||||
|
Service: api,
|
||||||
|
Version: "1.0"},
|
||||||
|
}
|
||||||
|
if c.Bool(utils.RPCEnabledFlag.Name) {
|
||||||
|
|
||||||
|
vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
|
||||||
|
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
|
||||||
|
|
||||||
|
// start http server
|
||||||
|
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
|
||||||
|
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Could not start RPC api: %v", err)
|
||||||
|
}
|
||||||
|
extapiURL = fmt.Sprintf("http://%s", httpEndpoint)
|
||||||
|
log.Info("HTTP endpoint opened", "url", extapiURL)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
listener.Close()
|
||||||
|
log.Info("HTTP endpoint closed", "url", httpEndpoint)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
if !c.Bool(utils.IPCDisabledFlag.Name) {
|
||||||
|
if c.IsSet(utils.IPCPathFlag.Name) {
|
||||||
|
ipcapiURL = c.String(utils.IPCPathFlag.Name)
|
||||||
|
} else {
|
||||||
|
ipcapiURL = filepath.Join(configDir, "clef.ipc")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, _, err := rpc.StartIPCEndpoint(ipcapiURL, rpcAPI)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Could not start IPC api: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("IPC endpoint opened", "url", ipcapiURL)
|
||||||
|
defer func() {
|
||||||
|
listener.Close()
|
||||||
|
log.Info("IPC endpoint closed", "url", ipcapiURL)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Bool(testFlag.Name) {
|
||||||
|
log.Info("Performing UI test")
|
||||||
|
go testExternalUI(apiImpl)
|
||||||
|
}
|
||||||
|
ui.OnSignerStartup(core.StartupInfo{
|
||||||
|
Info: map[string]interface{}{
|
||||||
|
"extapi_version": ExternalAPIVersion,
|
||||||
|
"intapi_version": InternalAPIVersion,
|
||||||
|
"extapi_http": extapiURL,
|
||||||
|
"extapi_ipc": ipcapiURL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
abortChan := make(chan os.Signal)
|
||||||
|
signal.Notify(abortChan, os.Interrupt)
|
||||||
|
|
||||||
|
sig := <-abortChan
|
||||||
|
log.Info("Exiting...", "signal", sig)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitAndTrim splits input separated by a comma
|
||||||
|
// and trims excessive white space from the substrings.
|
||||||
|
func splitAndTrim(input string) []string {
|
||||||
|
result := strings.Split(input, ",")
|
||||||
|
for i, r := range result {
|
||||||
|
result[i] = strings.TrimSpace(r)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfigDir is the default config directory to use for the vaults and other
|
||||||
|
// persistence requirements.
|
||||||
|
func DefaultConfigDir() string {
|
||||||
|
// Try to place the data folder in the user's home dir
|
||||||
|
home := homeDir()
|
||||||
|
if home != "" {
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
return filepath.Join(home, "Library", "Signer")
|
||||||
|
} else if runtime.GOOS == "windows" {
|
||||||
|
return filepath.Join(home, "AppData", "Roaming", "Signer")
|
||||||
|
} else {
|
||||||
|
return filepath.Join(home, ".clef")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// As we cannot guess a stable location, return empty and handle later
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func homeDir() string {
|
||||||
|
if home := os.Getenv("HOME"); home != "" {
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
if usr, err := user.Current(); err == nil {
|
||||||
|
return usr.HomeDir
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
func readMasterKey(ctx *cli.Context) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
file string
|
||||||
|
configDir = ctx.String(configdirFlag.Name)
|
||||||
|
)
|
||||||
|
if ctx.IsSet(signerSecretFlag.Name) {
|
||||||
|
file = ctx.String(signerSecretFlag.Name)
|
||||||
|
} else {
|
||||||
|
file = filepath.Join(configDir, "secrets.dat")
|
||||||
|
}
|
||||||
|
if err := checkFile(file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
masterKey, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(masterKey) < 256 {
|
||||||
|
return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey))
|
||||||
|
}
|
||||||
|
// Create vault location
|
||||||
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10]))
|
||||||
|
err = os.Mkdir(vaultLocation, 0700)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//!TODO, use KDF to stretch the master key
|
||||||
|
// stretched_key := stretch_key(master_key)
|
||||||
|
|
||||||
|
return masterKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkFile is a convenience function to check if a file
|
||||||
|
// * exists
|
||||||
|
// * is mode 0600
|
||||||
|
func checkFile(filename string) error {
|
||||||
|
info, err := os.Stat(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed stat on %s: %v", filename, err)
|
||||||
|
}
|
||||||
|
// Check the unix permission bits
|
||||||
|
if info.Mode().Perm()&077 != 0 {
|
||||||
|
return fmt.Errorf("file (%v) has insecure file permissions (%v)", filename, info.Mode().String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// confirm displays a text and asks for user confirmation
|
||||||
|
func confirm(text string) bool {
|
||||||
|
fmt.Printf(text)
|
||||||
|
fmt.Printf("\nEnter 'ok' to proceed:\n>")
|
||||||
|
|
||||||
|
text, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to read user input", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if text := strings.TrimSpace(text); text == "ok" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExternalUI(api *core.SignerAPI) {
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.Background(), "remote", "clef binary")
|
||||||
|
ctx = context.WithValue(ctx, "scheme", "in-proc")
|
||||||
|
ctx = context.WithValue(ctx, "local", "main")
|
||||||
|
|
||||||
|
errs := make([]string, 0)
|
||||||
|
|
||||||
|
api.UI.ShowInfo("Testing 'ShowInfo'")
|
||||||
|
api.UI.ShowError("Testing 'ShowError'")
|
||||||
|
|
||||||
|
checkErr := func(method string, err error) {
|
||||||
|
if err != nil && err != core.ErrRequestDenied {
|
||||||
|
errs = append(errs, fmt.Sprintf("%v: %v", method, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
_, err = api.SignTransaction(ctx, core.SendTxArgs{From: common.MixedcaseAddress{}}, nil)
|
||||||
|
checkErr("SignTransaction", err)
|
||||||
|
_, err = api.Sign(ctx, common.MixedcaseAddress{}, common.Hex2Bytes("01020304"))
|
||||||
|
checkErr("Sign", err)
|
||||||
|
_, err = api.List(ctx)
|
||||||
|
checkErr("List", err)
|
||||||
|
_, err = api.New(ctx)
|
||||||
|
checkErr("New", err)
|
||||||
|
_, err = api.Export(ctx, common.Address{})
|
||||||
|
checkErr("Export", err)
|
||||||
|
_, err = api.Import(ctx, json.RawMessage{})
|
||||||
|
checkErr("Import", err)
|
||||||
|
|
||||||
|
api.UI.ShowInfo("Tests completed")
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
log.Error("Got errors")
|
||||||
|
for _, e := range errs {
|
||||||
|
log.Error(e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Info("No errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
//Create Account
|
||||||
|
|
||||||
|
curl -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_new","params":["test"],"id":67}' localhost:8550
|
||||||
|
|
||||||
|
// List accounts
|
||||||
|
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_list","params":[""],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
|
// Make Transaction
|
||||||
|
// safeSend(0x12)
|
||||||
|
// 4401a6e40000000000000000000000000000000000000000000000000000000000000012
|
||||||
|
|
||||||
|
// supplied abi
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"},"test"],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
|
// Not supplied
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_signTransaction","params":[{"from":"0x82A2A876D39022B3019932D30Cd9c97ad5616813","gas":"0x333","gasPrice":"0x123","nonce":"0x0","to":"0x07a565b7ed7d7a678680a4c162885bedbb695fe0", "value":"0x10", "data":"0x4401a6e40000000000000000000000000000000000000000000000000000000000000012"}],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
|
// Sign data
|
||||||
|
|
||||||
|
curl -i -H "Content-Type: application/json" -X POST --data '{"jsonrpc":"2.0","method":"account_sign","params":["0x694267f14675d7e1b9494fd8d72fefe1755710fa","bazonk gaz baz"],"id":67}' http://localhost:8550/
|
||||||
|
|
||||||
|
|
||||||
|
**/
|
||||||
179
cmd/clef/pythonsigner.py
Normal file
179
cmd/clef/pythonsigner.py
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
import os,sys, subprocess
|
||||||
|
from tinyrpc.transports import ServerTransport
|
||||||
|
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
|
||||||
|
from tinyrpc.dispatch import public,RPCDispatcher
|
||||||
|
from tinyrpc.server import RPCServer
|
||||||
|
|
||||||
|
""" This is a POC example of how to write a custom UI for Clef. The UI starts the
|
||||||
|
clef process with the '--stdio-ui' option, and communicates with clef using standard input / output.
|
||||||
|
|
||||||
|
The standard input/output is a relatively secure way to communicate, as it does not require opening any ports
|
||||||
|
or IPC files. Needless to say, it does not protect against memory inspection mechanisms where an attacker
|
||||||
|
can access process memory."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
except ImportError:
|
||||||
|
import urllib as urlparse
|
||||||
|
|
||||||
|
class StdIOTransport(ServerTransport):
|
||||||
|
""" Uses std input/output for RPC """
|
||||||
|
def receive_message(self):
|
||||||
|
return None, urlparse.unquote(sys.stdin.readline())
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
print(reply)
|
||||||
|
|
||||||
|
class PipeTransport(ServerTransport):
|
||||||
|
""" Uses std a pipe for RPC """
|
||||||
|
|
||||||
|
def __init__(self,input, output):
|
||||||
|
self.input = input
|
||||||
|
self.output = output
|
||||||
|
|
||||||
|
def receive_message(self):
|
||||||
|
data = self.input.readline()
|
||||||
|
print(">> {}".format( data))
|
||||||
|
return None, urlparse.unquote(data)
|
||||||
|
|
||||||
|
def send_reply(self, context, reply):
|
||||||
|
print("<< {}".format( reply))
|
||||||
|
self.output.write(reply)
|
||||||
|
self.output.write("\n")
|
||||||
|
|
||||||
|
class StdIOHandler():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ApproveTx(self,req):
|
||||||
|
"""
|
||||||
|
Example request:
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "ApproveTx",
|
||||||
|
"params": [{
|
||||||
|
"transaction": {
|
||||||
|
"to": "0xae967917c465db8578ca9024c205720b1a3651A9",
|
||||||
|
"gas": "0x333",
|
||||||
|
"gasPrice": "0x123",
|
||||||
|
"value": "0x10",
|
||||||
|
"data": "0xd7a5865800000000000000000000000000000000000000000000000000000000000000ff",
|
||||||
|
"nonce": "0x0"
|
||||||
|
},
|
||||||
|
"from": "0xAe967917c465db8578ca9024c205720b1a3651A9",
|
||||||
|
"call_info": "Warning! Could not validate ABI-data against calldata\nSupplied ABI spec does not contain method signature in data: 0xd7a58658",
|
||||||
|
"meta": {
|
||||||
|
"remote": "127.0.0.1:34572",
|
||||||
|
"local": "localhost:8550",
|
||||||
|
"scheme": "HTTP/1.1"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
"id": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
:param transaction: transaction info
|
||||||
|
:param call_info: info abou the call, e.g. if ABI info could not be
|
||||||
|
:param meta: metadata about the request, e.g. where the call comes from
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
transaction = req.get('transaction')
|
||||||
|
_from = req.get('from')
|
||||||
|
call_info = req.get('call_info')
|
||||||
|
meta = req.get('meta')
|
||||||
|
|
||||||
|
return {
|
||||||
|
"approved" : False,
|
||||||
|
#"transaction" : transaction,
|
||||||
|
# "from" : _from,
|
||||||
|
# "password" : None,
|
||||||
|
}
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ApproveSignData(self, req):
|
||||||
|
""" Example request
|
||||||
|
|
||||||
|
"""
|
||||||
|
return {"approved": False, "password" : None}
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ApproveExport(self, req):
|
||||||
|
""" Example request
|
||||||
|
|
||||||
|
"""
|
||||||
|
return {"approved" : False}
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ApproveImport(self, req):
|
||||||
|
""" Example request
|
||||||
|
|
||||||
|
"""
|
||||||
|
return { "approved" : False, "old_password": "", "new_password": ""}
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ApproveListing(self, req):
|
||||||
|
""" Example request
|
||||||
|
|
||||||
|
"""
|
||||||
|
return {'accounts': []}
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ApproveNewAccount(self, req):
|
||||||
|
"""
|
||||||
|
Example request
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return {"approved": False,
|
||||||
|
#"password": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ShowError(self,message = {}):
|
||||||
|
"""
|
||||||
|
Example request:
|
||||||
|
|
||||||
|
{"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowError'"},"id":1}
|
||||||
|
|
||||||
|
:param message: to show
|
||||||
|
:return: nothing
|
||||||
|
"""
|
||||||
|
if 'text' in message.keys():
|
||||||
|
sys.stderr.write("Error: {}\n".format( message['text']))
|
||||||
|
return
|
||||||
|
|
||||||
|
@public
|
||||||
|
def ShowInfo(self,message = {}):
|
||||||
|
"""
|
||||||
|
Example request
|
||||||
|
{"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowInfo'"},"id":0}
|
||||||
|
|
||||||
|
:param message: to display
|
||||||
|
:return:nothing
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 'text' in message.keys():
|
||||||
|
sys.stdout.write("Error: {}\n".format( message['text']))
|
||||||
|
return
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
|
||||||
|
cmd = ["./clef", "--stdio-ui"]
|
||||||
|
if len(args) > 0 and args[0] == "test":
|
||||||
|
cmd.extend(["--stdio-ui-test"])
|
||||||
|
print("cmd: {}".format(" ".join(cmd)))
|
||||||
|
dispatcher = RPCDispatcher()
|
||||||
|
dispatcher.register_instance(StdIOHandler(), '')
|
||||||
|
# line buffered
|
||||||
|
p = subprocess.Popen(cmd, bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||||
|
|
||||||
|
rpc_server = RPCServer(
|
||||||
|
PipeTransport(p.stdout, p.stdin),
|
||||||
|
JSONRPCProtocol(),
|
||||||
|
dispatcher
|
||||||
|
)
|
||||||
|
rpc_server.serve_forever()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
||||||
236
cmd/clef/rules.md
Normal file
236
cmd/clef/rules.md
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
# Rules
|
||||||
|
|
||||||
|
The `signer` binary contains a ruleset engine, implemented with [OttoVM](https://github.com/robertkrimen/otto)
|
||||||
|
|
||||||
|
It enables usecases like the following:
|
||||||
|
|
||||||
|
* I want to auto-approve transactions with contract `CasinoDapp`, with up to `0.05 ether` in value to maximum `1 ether` per 24h period
|
||||||
|
* I want to auto-approve transaction to contract `EthAlarmClock` with `data`=`0xdeadbeef`, if `value=0`, `gas < 44k` and `gasPrice < 40Gwei`
|
||||||
|
|
||||||
|
The two main features that are required for this to work well are;
|
||||||
|
|
||||||
|
1. Rule Implementation: how to create, manage and interpret rules in a flexible but secure manner
|
||||||
|
2. Credential managements and credentials; how to provide auto-unlock without exposing keys unnecessarily.
|
||||||
|
|
||||||
|
The section below deals with both of them
|
||||||
|
|
||||||
|
## Rule Implementation
|
||||||
|
|
||||||
|
A ruleset file is implemented as a `js` file. Under the hood, the ruleset-engine is a `SignerUI`, implementing the same methods as the `json-rpc` methods
|
||||||
|
defined in the UI protocol. Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
function asBig(str){
|
||||||
|
if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
|
||||||
|
return new BigNumber(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Approve transactions to a certain contract if value is below a certain limit
|
||||||
|
function ApproveTx(req){
|
||||||
|
|
||||||
|
var limit = big.Newint("0xb1a2bc2ec50000")
|
||||||
|
var value = asBig(req.transaction.value);
|
||||||
|
|
||||||
|
if(req.transaction.to.toLowerCase()=="0xae967917c465db8578ca9024c205720b1a3651a9")
|
||||||
|
&& value.lt(limit) ){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
// If we return "Reject", it will be rejected.
|
||||||
|
// By not returning anything, it will be passed to the next UI, for manual processing
|
||||||
|
}
|
||||||
|
|
||||||
|
//Approve listings if request made from IPC
|
||||||
|
function ApproveListing(req){
|
||||||
|
if (req.metadata.scheme == "ipc"){ return "Approve"}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Whenever the external API is called (and the ruleset is enabled), the `signer` calls the UI, which is an instance of a ruleset-engine. The ruleset-engine
|
||||||
|
invokes the corresponding method. In doing so, there are three possible outcomes:
|
||||||
|
|
||||||
|
1. JS returns "Approve"
|
||||||
|
* Auto-approve request
|
||||||
|
2. JS returns "Reject"
|
||||||
|
* Auto-reject request
|
||||||
|
3. Error occurs, or something else is returned
|
||||||
|
* Pass on to `next` ui: the regular UI channel.
|
||||||
|
|
||||||
|
A more advanced example can be found below, "Example 1: ruleset for a rate-limited window", using `storage` to `Put` and `Get` `string`s by key.
|
||||||
|
|
||||||
|
* At the time of writing, storage only exists as an ephemeral unencrypted implementation, to be used during testing.
|
||||||
|
|
||||||
|
### Things to note
|
||||||
|
|
||||||
|
The Otto vm has a few [caveats](https://github.com/robertkrimen/otto):
|
||||||
|
|
||||||
|
* "use strict" will parse, but does nothing.
|
||||||
|
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
|
||||||
|
* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported.
|
||||||
|
|
||||||
|
Additionally, a few more have been added
|
||||||
|
|
||||||
|
* The rule execution cannot load external javascript files.
|
||||||
|
* The only preloaded libary is [`bignumber.js`](https://github.com/MikeMcl/bignumber.js) version `2.0.3`. This one is fairly old, and is not aligned with the documentation at the github repository.
|
||||||
|
* Each invocation is made in a fresh virtual machine. This means that you cannot store data in global variables between invocations. This is a deliberate choice -- if you want to store data, use the disk-backed `storage`, since rules should not rely on ephemeral data.
|
||||||
|
* Javascript API parameters are _always_ an object. This is also a design choice, to ensure that parameters are accessed by _key_ and not by order. This is to prevent mistakes due to missing parameters or parameter changes.
|
||||||
|
* The JS engine has access to `storage` and `console`.
|
||||||
|
|
||||||
|
#### Security considerations
|
||||||
|
|
||||||
|
##### Security of ruleset
|
||||||
|
|
||||||
|
Some security precautions can be made, such as:
|
||||||
|
|
||||||
|
* Never load `ruleset.js` unless the file is `readonly` (`r-??-??-?`). If the user wishes to modify the ruleset, he must make it writeable and then set back to readonly.
|
||||||
|
* This is to prevent attacks where files are dropped on the users disk.
|
||||||
|
* Since we're going to have to have some form of secure storage (not defined in this section), we could also store the `sha3` of the `ruleset.js` file in there.
|
||||||
|
* If the user wishes to modify the ruleset, he'd then have to perform e.g. `signer --attest /path/to/ruleset --credential <creds>`
|
||||||
|
|
||||||
|
##### Security of implementation
|
||||||
|
|
||||||
|
The drawbacks of this very flexible solution is that the `signer` needs to contain a javascript engine. This is pretty simple to implement, since it's already
|
||||||
|
implemented for `geth`. There are no known security vulnerabilities in, nor have we had any security-problems with it so far.
|
||||||
|
|
||||||
|
The javascript engine would be an added attack surface; but if the validation of `rulesets` is made good (with hash-based attestation), the actual javascript cannot be considered
|
||||||
|
an attack surface -- if an attacker can control the ruleset, a much simpler attack would be to implement an "always-approve" rule instead of exploiting the js vm. The only benefit
|
||||||
|
to be gained from attacking the actual `signer` process from the `js` side would be if it could somehow extract cryptographic keys from memory.
|
||||||
|
|
||||||
|
##### Security in usability
|
||||||
|
|
||||||
|
Javascript is flexible, but also easy to get wrong, especially when users assume that `js` can handle large integers natively. Typical errors
|
||||||
|
include trying to multiply `gasCost` with `gas` without using `bigint`:s.
|
||||||
|
|
||||||
|
It's unclear whether any other DSL could be more secure; since there's always the possibility of erroneously implementing a rule.
|
||||||
|
|
||||||
|
|
||||||
|
## Credential management
|
||||||
|
|
||||||
|
The ability to auto-approve transaction means that the signer needs to have necessary credentials to decrypt keyfiles. These passwords are hereafter called `ksp` (keystore pass).
|
||||||
|
|
||||||
|
### Example implementation
|
||||||
|
|
||||||
|
Upon startup of the signer, the signer is given a switch: `--seed <path/to/masterseed>`
|
||||||
|
The `seed` contains a blob of bytes, which is the master seed for the `signer`.
|
||||||
|
|
||||||
|
The `signer` uses the `seed` to:
|
||||||
|
|
||||||
|
* Generate the `path` where the settings are stored.
|
||||||
|
* `./settings/1df094eb-c2b1-4689-90dd-790046d38025/vault.dat`
|
||||||
|
* `./settings/1df094eb-c2b1-4689-90dd-790046d38025/rules.js`
|
||||||
|
* Generate the encryption password for `vault.dat`.
|
||||||
|
|
||||||
|
The `vault.dat` would be an encrypted container storing the following information:
|
||||||
|
|
||||||
|
* `ksp` entries
|
||||||
|
* `sha256` hash of `rules.js`
|
||||||
|
* Information about pair:ed callers (not yet specified)
|
||||||
|
|
||||||
|
### Security considerations
|
||||||
|
|
||||||
|
This would leave it up to the user to ensure that the `path/to/masterseed` is handled in a secure way. It's difficult to get around this, although one could
|
||||||
|
imagine leveraging OS-level keychains where supported. The setup is however in general similar to how ssh-keys are stored in `.ssh/`.
|
||||||
|
|
||||||
|
|
||||||
|
# Implementation status
|
||||||
|
|
||||||
|
This is now implemented (with ephemeral non-encrypted storage for now, so not yet enabled).
|
||||||
|
|
||||||
|
## Example 1: ruleset for a rate-limited window
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
function big(str){
|
||||||
|
if(str.slice(0,2) == "0x"){ return new BigNumber(str.slice(2),16)}
|
||||||
|
return new BigNumber(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time window: 1 week
|
||||||
|
var window = 1000* 3600*24*7;
|
||||||
|
|
||||||
|
// Limit : 1 ether
|
||||||
|
var limit = new BigNumber("1e18");
|
||||||
|
|
||||||
|
function isLimitOk(transaction){
|
||||||
|
var value = big(transaction.value)
|
||||||
|
// Start of our window function
|
||||||
|
var windowstart = new Date().getTime() - window;
|
||||||
|
|
||||||
|
var txs = [];
|
||||||
|
var stored = storage.Get('txs');
|
||||||
|
|
||||||
|
if(stored != ""){
|
||||||
|
txs = JSON.parse(stored)
|
||||||
|
}
|
||||||
|
// First, remove all that have passed out of the time-window
|
||||||
|
var newtxs = txs.filter(function(tx){return tx.tstamp > windowstart});
|
||||||
|
console.log(txs, newtxs.length);
|
||||||
|
|
||||||
|
// Secondly, aggregate the current sum
|
||||||
|
sum = new BigNumber(0)
|
||||||
|
|
||||||
|
sum = newtxs.reduce(function(agg, tx){ return big(tx.value).plus(agg)}, sum);
|
||||||
|
console.log("ApproveTx > Sum so far", sum);
|
||||||
|
console.log("ApproveTx > Requested", value.toNumber());
|
||||||
|
|
||||||
|
// Would we exceed weekly limit ?
|
||||||
|
return sum.plus(value).lt(limit)
|
||||||
|
|
||||||
|
}
|
||||||
|
function ApproveTx(r){
|
||||||
|
if (isLimitOk(r.transaction)){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
return "Nope"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OnApprovedTx(str) is called when a transaction has been approved and signed. The parameter
|
||||||
|
* 'response_str' contains the return value that will be sent to the external caller.
|
||||||
|
* The return value from this method is ignore - the reason for having this callback is to allow the
|
||||||
|
* ruleset to keep track of approved transactions.
|
||||||
|
*
|
||||||
|
* When implementing rate-limited rules, this callback should be used.
|
||||||
|
* If a rule responds with neither 'Approve' nor 'Reject' - the tx goes to manual processing. If the user
|
||||||
|
* then accepts the transaction, this method will be called.
|
||||||
|
*
|
||||||
|
* TLDR; Use this method to keep track of signed transactions, instead of using the data in ApproveTx.
|
||||||
|
*/
|
||||||
|
function OnApprovedTx(resp){
|
||||||
|
var value = big(resp.tx.value)
|
||||||
|
var txs = []
|
||||||
|
// Load stored transactions
|
||||||
|
var stored = storage.Get('txs');
|
||||||
|
if(stored != ""){
|
||||||
|
txs = JSON.parse(stored)
|
||||||
|
}
|
||||||
|
// Add this to the storage
|
||||||
|
txs.push({tstamp: new Date().getTime(), value: value});
|
||||||
|
storage.Put("txs", JSON.stringify(txs));
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 2: allow destination
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
function ApproveTx(r){
|
||||||
|
if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"}
|
||||||
|
if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"}
|
||||||
|
// Otherwise goes to manual processing
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example 3: Allow listing
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
|
function ApproveListing(){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
BIN
cmd/clef/sign_flow.png
Normal file
BIN
cmd/clef/sign_flow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
198
cmd/clef/tutorial.md
Normal file
198
cmd/clef/tutorial.md
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
## Initializing the signer
|
||||||
|
|
||||||
|
First, initialize the master seed.
|
||||||
|
|
||||||
|
```text
|
||||||
|
#./signer init
|
||||||
|
|
||||||
|
WARNING!
|
||||||
|
|
||||||
|
The signer is alpha software, and not yet publically released. This software has _not_ been audited, and there
|
||||||
|
are no guarantees about the workings of this software. It may contain severe flaws. You should not use this software
|
||||||
|
unless you agree to take full responsibility for doing so, and know what you are doing.
|
||||||
|
|
||||||
|
TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE!
|
||||||
|
|
||||||
|
|
||||||
|
Enter 'ok' to proceed:
|
||||||
|
>ok
|
||||||
|
A master seed has been generated into /home/martin/.signer/secrets.dat
|
||||||
|
|
||||||
|
This is required to be able to store credentials, such as :
|
||||||
|
* Passwords for keystores (used by rule engine)
|
||||||
|
* Storage for javascript rules
|
||||||
|
* Hash of rule-file
|
||||||
|
|
||||||
|
You should treat that file with utmost secrecy, and make a backup of it.
|
||||||
|
NOTE: This file does not contain your accounts. Those need to be backed up separately!
|
||||||
|
```
|
||||||
|
|
||||||
|
(for readability purposes, we'll remove the WARNING printout in the rest of this document)
|
||||||
|
|
||||||
|
## Creating rules
|
||||||
|
|
||||||
|
Now, you can create a rule-file.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function ApproveListing(){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Get the `sha256` hash....
|
||||||
|
```text
|
||||||
|
#sha256sum rules.js
|
||||||
|
6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72 rules.js
|
||||||
|
```
|
||||||
|
...And then `attest` the file:
|
||||||
|
```text
|
||||||
|
#./signer attest 6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72
|
||||||
|
|
||||||
|
INFO [02-21|12:14:38] Ruleset attestation updated sha256=6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72
|
||||||
|
```
|
||||||
|
At this point, we then start the signer with the rule-file:
|
||||||
|
|
||||||
|
```text
|
||||||
|
#./signer --rules rules.json
|
||||||
|
|
||||||
|
INFO [02-21|12:15:18] Using CLI as UI-channel
|
||||||
|
INFO [02-21|12:15:18] Loaded 4byte db signatures=5509 file=./4byte.json
|
||||||
|
INFO [02-21|12:15:18] Could not load rulefile, rules not enabled file=rulefile
|
||||||
|
DEBUG[02-21|12:15:18] FS scan times list=35.335µs set=5.536µs diff=5.073µs
|
||||||
|
DEBUG[02-21|12:15:18] Ledger support enabled
|
||||||
|
DEBUG[02-21|12:15:18] Trezor support enabled
|
||||||
|
INFO [02-21|12:15:18] Audit logs configured file=audit.log
|
||||||
|
INFO [02-21|12:15:18] HTTP endpoint opened url=http://localhost:8550
|
||||||
|
------- Signer info -------
|
||||||
|
* extapi_http : http://localhost:8550
|
||||||
|
* extapi_ipc : <nil>
|
||||||
|
* extapi_version : 2.0.0
|
||||||
|
* intapi_version : 1.2.0
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Any list-requests will now be auto-approved by our rule-file.
|
||||||
|
|
||||||
|
## Under the hood
|
||||||
|
|
||||||
|
While doing the operations above, these files have been created:
|
||||||
|
|
||||||
|
```text
|
||||||
|
#ls -laR ~/.signer/
|
||||||
|
/home/martin/.signer/:
|
||||||
|
total 16
|
||||||
|
drwx------ 3 martin martin 4096 feb 21 12:14 .
|
||||||
|
drwxr-xr-x 71 martin martin 4096 feb 21 12:12 ..
|
||||||
|
drwx------ 2 martin martin 4096 feb 21 12:14 43f73718397aa54d1b22
|
||||||
|
-rwx------ 1 martin martin 256 feb 21 12:12 secrets.dat
|
||||||
|
|
||||||
|
/home/martin/.signer/43f73718397aa54d1b22:
|
||||||
|
total 12
|
||||||
|
drwx------ 2 martin martin 4096 feb 21 12:14 .
|
||||||
|
drwx------ 3 martin martin 4096 feb 21 12:14 ..
|
||||||
|
-rw------- 1 martin martin 159 feb 21 12:14 config.json
|
||||||
|
|
||||||
|
#cat /home/martin/.signer/43f73718397aa54d1b22/config.json
|
||||||
|
{"ruleset_sha256":{"iv":"6v4W4tfJxj3zZFbl","c":"6dt5RTDiTq93yh1qDEjpsat/tsKG7cb+vr3sza26IPL2fvsQ6ZoqFx++CPUa8yy6fD9Bbq41L01ehkKHTG3pOAeqTW6zc/+t0wv3AB6xPmU="}}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
In `~/.signer`, the `secrets.dat` file was created, containing the `master_seed`.
|
||||||
|
The `master_seed` was then used to derive a few other things:
|
||||||
|
|
||||||
|
- `vault_location` : in this case `43f73718397aa54d1b22` .
|
||||||
|
- Thus, if you use a different `master_seed`, another `vault_location` will be used that does not conflict with each other.
|
||||||
|
- Example: `signer --signersecret /path/to/afile ...`
|
||||||
|
- `config.json` which is the encrypted key/value storage for configuration data, containing the key `ruleset_sha256`.
|
||||||
|
|
||||||
|
|
||||||
|
## Adding credentials
|
||||||
|
|
||||||
|
In order to make more useful rules; sign transactions, the signer needs access to the passwords needed to unlock keystores.
|
||||||
|
|
||||||
|
```text
|
||||||
|
#./signer addpw 0x694267f14675d7e1b9494fd8d72fefe1755710fa test
|
||||||
|
|
||||||
|
INFO [02-21|13:43:21] Credential store updated key=0x694267f14675d7e1b9494fd8d72fefe1755710fa
|
||||||
|
```
|
||||||
|
## More advanced rules
|
||||||
|
|
||||||
|
Now let's update the rules to make use of credentials
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function ApproveListing(){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
function ApproveSignData(r){
|
||||||
|
if( r.address.toLowerCase() == "0x694267f14675d7e1b9494fd8d72fefe1755710fa")
|
||||||
|
{
|
||||||
|
if(r.message.indexOf("bazonk") >= 0){
|
||||||
|
return "Approve"
|
||||||
|
}
|
||||||
|
return "Reject"
|
||||||
|
}
|
||||||
|
// Otherwise goes to manual processing
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
In this example,
|
||||||
|
* any requests to sign data with the account `0x694...` will be
|
||||||
|
* auto-approved if the message contains with `bazonk`,
|
||||||
|
* and auto-rejected if it does not.
|
||||||
|
* Any other signing-requests will be passed along for manual approve/reject.
|
||||||
|
|
||||||
|
..attest the new file
|
||||||
|
```text
|
||||||
|
#sha256sum rules.js
|
||||||
|
2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f rules.js
|
||||||
|
|
||||||
|
#./signer attest 2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f
|
||||||
|
|
||||||
|
INFO [02-21|14:36:30] Ruleset attestation updated sha256=2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f
|
||||||
|
```
|
||||||
|
|
||||||
|
And start the signer:
|
||||||
|
|
||||||
|
```
|
||||||
|
#./signer --rules rules.js
|
||||||
|
|
||||||
|
INFO [02-21|14:41:56] Using CLI as UI-channel
|
||||||
|
INFO [02-21|14:41:56] Loaded 4byte db signatures=5509 file=./4byte.json
|
||||||
|
INFO [02-21|14:41:56] Rule engine configured file=rules.js
|
||||||
|
DEBUG[02-21|14:41:56] FS scan times list=34.607µs set=4.509µs diff=4.87µs
|
||||||
|
DEBUG[02-21|14:41:56] Ledger support enabled
|
||||||
|
DEBUG[02-21|14:41:56] Trezor support enabled
|
||||||
|
INFO [02-21|14:41:56] Audit logs configured file=audit.log
|
||||||
|
INFO [02-21|14:41:56] HTTP endpoint opened url=http://localhost:8550
|
||||||
|
------- Signer info -------
|
||||||
|
* extapi_version : 2.0.0
|
||||||
|
* intapi_version : 1.2.0
|
||||||
|
* extapi_http : http://localhost:8550
|
||||||
|
* extapi_ipc : <nil>
|
||||||
|
INFO [02-21|14:41:56] error occurred during execution error="ReferenceError: 'OnSignerStartup' is not defined"
|
||||||
|
```
|
||||||
|
And then test signing, once with `bazonk` and once without:
|
||||||
|
|
||||||
|
```
|
||||||
|
#curl -H "Content-Type: application/json" -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"account_sign\",\"params\":[\"0x694267f14675d7e1b9494fd8d72fefe1755710fa\",\"0x$(xxd -pu <<< ' bazonk baz gaz')\"],\"id\":67}" http://localhost:8550/
|
||||||
|
{"jsonrpc":"2.0","id":67,"result":"0x93e6161840c3ae1efc26dc68dedab6e8fc233bb3fefa1b4645dbf6609b93dace160572ea4ab33240256bb6d3dadb60dcd9c515d6374d3cf614ee897408d41d541c"}
|
||||||
|
|
||||||
|
#curl -H "Content-Type: application/json" -X POST --data "{\"jsonrpc\":\"2.0\",\"method\":\"account_sign\",\"params\":[\"0x694267f14675d7e1b9494fd8d72fefe1755710fa\",\"0x$(xxd -pu <<< ' bonk baz gaz')\"],\"id\":67}" http://localhost:8550/
|
||||||
|
{"jsonrpc":"2.0","id":67,"error":{"code":-32000,"message":"Request denied"}}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Meanwhile, in the signer output:
|
||||||
|
```text
|
||||||
|
INFO [02-21|14:42:41] Op approved
|
||||||
|
INFO [02-21|14:42:56] Op rejected
|
||||||
|
```
|
||||||
|
|
||||||
|
The signer also stores all traffic over the external API in a log file. The last 4 lines shows the two requests and their responses:
|
||||||
|
|
||||||
|
```text
|
||||||
|
#tail audit.log -n 4
|
||||||
|
t=2018-02-21T14:42:41+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49706\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=202062617a6f6e6b2062617a2067617a0a
|
||||||
|
t=2018-02-21T14:42:42+0100 lvl=info msg=Sign api=signer type=response data=93e6161840c3ae1efc26dc68dedab6e8fc233bb3fefa1b4645dbf6609b93dace160572ea4ab33240256bb6d3dadb60dcd9c515d6374d3cf614ee897408d41d541c error=nil
|
||||||
|
t=2018-02-21T14:42:56+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49708\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=2020626f6e6b2062617a2067617a0a
|
||||||
|
t=2018-02-21T14:42:56+0100 lvl=info msg=Sign api=signer type=response data= error="Request denied"
|
||||||
|
```
|
||||||
72
cmd/ethkey/changepassphrase.go
Normal file
72
cmd/ethkey/changepassphrase.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var newPassphraseFlag = cli.StringFlag{
|
||||||
|
Name: "newpasswordfile",
|
||||||
|
Usage: "the file that contains the new passphrase for the keyfile",
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandChangePassphrase = cli.Command{
|
||||||
|
Name: "changepassphrase",
|
||||||
|
Usage: "change the passphrase on a keyfile",
|
||||||
|
ArgsUsage: "<keyfile>",
|
||||||
|
Description: `
|
||||||
|
Change the passphrase of a keyfile.`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
passphraseFlag,
|
||||||
|
newPassphraseFlag,
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
keyfilepath := ctx.Args().First()
|
||||||
|
|
||||||
|
// Read key from file.
|
||||||
|
keyjson, err := ioutil.ReadFile(keyfilepath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt key with passphrase.
|
||||||
|
passphrase := getPassphrase(ctx)
|
||||||
|
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error decrypting key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a new passphrase.
|
||||||
|
fmt.Println("Please provide a new passphrase")
|
||||||
|
var newPhrase string
|
||||||
|
if passFile := ctx.String(newPassphraseFlag.Name); passFile != "" {
|
||||||
|
content, err := ioutil.ReadFile(passFile)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read new passphrase file '%s': %v", passFile, err)
|
||||||
|
}
|
||||||
|
newPhrase = strings.TrimRight(string(content), "\r\n")
|
||||||
|
} else {
|
||||||
|
newPhrase = promptPassphrase(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the key with the new passphrase.
|
||||||
|
newJson, err := keystore.EncryptKey(key, newPhrase, keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error encrypting with new passphrase: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then write the new keyfile in place of the old one.
|
||||||
|
if err := ioutil.WriteFile(keyfilepath, newJson, 600); err != nil {
|
||||||
|
utils.Fatalf("Error writing new keyfile to disk: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't print anything. Just return successfully,
|
||||||
|
// producing a positive exit code.
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -90,7 +90,7 @@ If you want to encrypt an existing private key, it can be specified by setting
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt key with passphrase.
|
// Encrypt key with passphrase.
|
||||||
passphrase := getPassPhrase(ctx, true)
|
passphrase := promptPassphrase(true)
|
||||||
keyjson, err := keystore.EncryptKey(key, passphrase, keystore.StandardScryptN, keystore.StandardScryptP)
|
keyjson, err := keystore.EncryptKey(key, passphrase, keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Error encrypting key: %v", err)
|
utils.Fatalf("Error encrypting key: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ make sure to use this feature with great caution!`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt key with passphrase.
|
// Decrypt key with passphrase.
|
||||||
passphrase := getPassPhrase(ctx, false)
|
passphrase := getPassphrase(ctx)
|
||||||
key, err := keystore.DecryptKey(keyjson, passphrase)
|
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Error decrypting key: %v", err)
|
utils.Fatalf("Error decrypting key: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ func init() {
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
commandGenerate,
|
commandGenerate,
|
||||||
commandInspect,
|
commandInspect,
|
||||||
|
commandChangePassphrase,
|
||||||
commandSignMessage,
|
commandSignMessage,
|
||||||
commandVerifyMessage,
|
commandVerifyMessage,
|
||||||
}
|
}
|
||||||
|
|
@ -53,10 +54,6 @@ var (
|
||||||
Name: "json",
|
Name: "json",
|
||||||
Usage: "output JSON instead of human-readable format",
|
Usage: "output JSON instead of human-readable format",
|
||||||
}
|
}
|
||||||
messageFlag = cli.StringFlag{
|
|
||||||
Name: "message",
|
|
||||||
Usage: "the file that contains the message to sign/verify",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ To sign a message contained in a file, use the --msgfile flag.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt key with passphrase.
|
// Decrypt key with passphrase.
|
||||||
passphrase := getPassPhrase(ctx, false)
|
passphrase := getPassphrase(ctx)
|
||||||
key, err := keystore.DecryptKey(keyjson, passphrase)
|
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Error decrypting key: %v", err)
|
utils.Fatalf("Error decrypting key: %v", err)
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,32 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getPassPhrase obtains a passphrase given by the user. It first checks the
|
// promptPassphrase prompts the user for a passphrase. Set confirmation to true
|
||||||
// --passphrase command line flag and ultimately prompts the user for a
|
// to require the user to confirm the passphrase.
|
||||||
|
func promptPassphrase(confirmation bool) string {
|
||||||
|
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirmation {
|
||||||
|
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
||||||
|
}
|
||||||
|
if passphrase != confirm {
|
||||||
|
utils.Fatalf("Passphrases do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPassphrase obtains a passphrase given by the user. It first checks the
|
||||||
|
// --passfile command line flag and ultimately prompts the user for a
|
||||||
// passphrase.
|
// passphrase.
|
||||||
func getPassPhrase(ctx *cli.Context, confirmation bool) string {
|
func getPassphrase(ctx *cli.Context) string {
|
||||||
// Look for the --passphrase flag.
|
// Look for the --passwordfile flag.
|
||||||
passphraseFile := ctx.String(passphraseFlag.Name)
|
passphraseFile := ctx.String(passphraseFlag.Name)
|
||||||
if passphraseFile != "" {
|
if passphraseFile != "" {
|
||||||
content, err := ioutil.ReadFile(passphraseFile)
|
content, err := ioutil.ReadFile(passphraseFile)
|
||||||
|
|
@ -44,20 +65,7 @@ func getPassPhrase(ctx *cli.Context, confirmation bool) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise prompt the user for the passphrase.
|
// Otherwise prompt the user for the passphrase.
|
||||||
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
|
return promptPassphrase(false)
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
|
||||||
}
|
|
||||||
if confirmation {
|
|
||||||
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
|
||||||
}
|
|
||||||
if passphrase != confirm {
|
|
||||||
utils.Fatalf("Passphrases do not match")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return passphrase
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// signHash is a helper function that calculates a hash for the given message
|
// signHash is a helper function that calculates a hash for the given message
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,6 @@ func compileCmd(ctx *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println(bin)
|
fmt.Fprintln(ctx.App.Writer, bin)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,6 @@ func disasmCmd(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
code := strings.TrimSpace(string(in[:]))
|
code := strings.TrimSpace(string(in[:]))
|
||||||
fmt.Printf("%v\n", code)
|
fmt.Fprintf(ctx.App.Writer, "%v\n", code)
|
||||||
return asm.PrintDisassembled(code)
|
return asm.PrintDisassembled(code)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,11 @@ func Compile(fn string, src []byte, debug bool) (string, error) {
|
||||||
bin, compileErrors := compiler.Compile()
|
bin, compileErrors := compiler.Compile()
|
||||||
if len(compileErrors) > 0 {
|
if len(compileErrors) > 0 {
|
||||||
// report errors
|
// report errors
|
||||||
|
errs := ""
|
||||||
for _, err := range compileErrors {
|
for _, err := range compileErrors {
|
||||||
fmt.Printf("%s:%v\n", fn, err)
|
errs += fmt.Sprintf("%s:%v\n", fn, err)
|
||||||
}
|
}
|
||||||
return "", errors.New("compiling failed")
|
return "", errors.New(errs + "compiling failed\n")
|
||||||
}
|
}
|
||||||
return bin, nil
|
return bin, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ type JSONLogger struct {
|
||||||
cfg *vm.LogConfig
|
cfg *vm.LogConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewJSONLogger creates a new EVM tracer that prints execution steps as JSON objects
|
||||||
|
// into the provided stream.
|
||||||
func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
|
func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
|
||||||
return &JSONLogger{json.NewEncoder(writer), cfg}
|
return &JSONLogger{json.NewEncoder(writer), cfg}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
goruntime "runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
goruntime "runtime"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
|
"github.com/ethereum/go-ethereum/cmd/evm/internal/compiler"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -76,6 +76,7 @@ func runCmd(ctx *cli.Context) error {
|
||||||
logconfig := &vm.LogConfig{
|
logconfig := &vm.LogConfig{
|
||||||
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name),
|
||||||
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
DisableStack: ctx.GlobalBool(DisableStackFlag.Name),
|
||||||
|
Debug: ctx.GlobalBool(DebugFlag.Name),
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -83,8 +84,9 @@ func runCmd(ctx *cli.Context) error {
|
||||||
debugLogger *vm.StructLogger
|
debugLogger *vm.StructLogger
|
||||||
statedb *state.StateDB
|
statedb *state.StateDB
|
||||||
chainConfig *params.ChainConfig
|
chainConfig *params.ChainConfig
|
||||||
sender = common.StringToAddress("sender")
|
sender = common.BytesToAddress([]byte("sender"))
|
||||||
receiver = common.StringToAddress("receiver")
|
receiver = common.BytesToAddress([]byte("receiver"))
|
||||||
|
blockNumber uint64
|
||||||
)
|
)
|
||||||
if ctx.GlobalBool(MachineFlag.Name) {
|
if ctx.GlobalBool(MachineFlag.Name) {
|
||||||
tracer = NewJSONLogger(logconfig, os.Stdout)
|
tracer = NewJSONLogger(logconfig, os.Stdout)
|
||||||
|
|
@ -96,13 +98,13 @@ func runCmd(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
if ctx.GlobalString(GenesisFlag.Name) != "" {
|
if ctx.GlobalString(GenesisFlag.Name) != "" {
|
||||||
gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
|
gen := readGenesis(ctx.GlobalString(GenesisFlag.Name))
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db := ethdb.NewMemDatabase()
|
||||||
genesis := gen.ToBlock(db)
|
genesis := gen.ToBlock(db)
|
||||||
statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
|
statedb, _ = state.New(genesis.Root(), state.NewDatabase(db))
|
||||||
chainConfig = gen.Config
|
chainConfig = gen.Config
|
||||||
|
blockNumber = gen.Number
|
||||||
} else {
|
} else {
|
||||||
db, _ := ethdb.NewMemDatabase()
|
statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||||
statedb, _ = state.New(common.Hash{}, state.NewDatabase(db))
|
|
||||||
}
|
}
|
||||||
if ctx.GlobalString(SenderFlag.Name) != "" {
|
if ctx.GlobalString(SenderFlag.Name) != "" {
|
||||||
sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))
|
sender = common.HexToAddress(ctx.GlobalString(SenderFlag.Name))
|
||||||
|
|
@ -126,13 +128,13 @@ func runCmd(ctx *cli.Context) error {
|
||||||
if ctx.GlobalString(CodeFileFlag.Name) == "-" {
|
if ctx.GlobalString(CodeFileFlag.Name) == "-" {
|
||||||
//Try reading from stdin
|
//Try reading from stdin
|
||||||
if hexcode, err = ioutil.ReadAll(os.Stdin); err != nil {
|
if hexcode, err = ioutil.ReadAll(os.Stdin); err != nil {
|
||||||
fmt.Printf("Could not load code from stdin: %v\n", err)
|
fmt.Fprintf(ctx.App.ErrWriter, "Could not load code from stdin: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Codefile with hex assembly
|
// Codefile with hex assembly
|
||||||
if hexcode, err = ioutil.ReadFile(ctx.GlobalString(CodeFileFlag.Name)); err != nil {
|
if hexcode, err = ioutil.ReadFile(ctx.GlobalString(CodeFileFlag.Name)); err != nil {
|
||||||
fmt.Printf("Could not load code from file: %v\n", err)
|
fmt.Fprintf(ctx.App.ErrWriter, "Could not load code from file: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -160,6 +162,7 @@ func runCmd(ctx *cli.Context) error {
|
||||||
GasLimit: initialGas,
|
GasLimit: initialGas,
|
||||||
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
|
GasPrice: utils.GlobalBig(ctx, PriceFlag.Name),
|
||||||
Value: utils.GlobalBig(ctx, ValueFlag.Name),
|
Value: utils.GlobalBig(ctx, ValueFlag.Name),
|
||||||
|
BlockNumber: new(big.Int).SetUint64(blockNumber),
|
||||||
EVMConfig: vm.Config{
|
EVMConfig: vm.Config{
|
||||||
Tracer: tracer,
|
Tracer: tracer,
|
||||||
Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name),
|
Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name),
|
||||||
|
|
@ -169,11 +172,11 @@ func runCmd(ctx *cli.Context) error {
|
||||||
if cpuProfilePath := ctx.GlobalString(CPUProfileFlag.Name); cpuProfilePath != "" {
|
if cpuProfilePath := ctx.GlobalString(CPUProfileFlag.Name); cpuProfilePath != "" {
|
||||||
f, err := os.Create(cpuProfilePath)
|
f, err := os.Create(cpuProfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("could not create CPU profile: ", err)
|
fmt.Fprintf(ctx.App.ErrWriter, "could not create CPU profile: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
fmt.Println("could not start CPU profile: ", err)
|
fmt.Fprintf(ctx.App.ErrWriter, "could not start CPU profile: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
|
|
@ -197,17 +200,17 @@ func runCmd(ctx *cli.Context) error {
|
||||||
|
|
||||||
if ctx.GlobalBool(DumpFlag.Name) {
|
if ctx.GlobalBool(DumpFlag.Name) {
|
||||||
statedb.IntermediateRoot(true)
|
statedb.IntermediateRoot(true)
|
||||||
fmt.Println(string(statedb.Dump()))
|
fmt.Fprintln(ctx.App.Writer, string(statedb.Dump()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if memProfilePath := ctx.GlobalString(MemProfileFlag.Name); memProfilePath != "" {
|
if memProfilePath := ctx.GlobalString(MemProfileFlag.Name); memProfilePath != "" {
|
||||||
f, err := os.Create(memProfilePath)
|
f, err := os.Create(memProfilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("could not create memory profile: ", err)
|
fmt.Fprintf(ctx.App.ErrWriter, "could not create memory profile: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
fmt.Println("could not write memory profile: ", err)
|
fmt.Fprintf(ctx.App.ErrWriter, "could not create memory profile: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
@ -215,17 +218,17 @@ func runCmd(ctx *cli.Context) error {
|
||||||
|
|
||||||
if ctx.GlobalBool(DebugFlag.Name) {
|
if ctx.GlobalBool(DebugFlag.Name) {
|
||||||
if debugLogger != nil {
|
if debugLogger != nil {
|
||||||
fmt.Fprintln(os.Stderr, "#### TRACE ####")
|
fmt.Fprintln(ctx.App.ErrWriter, "#### TRACE ####")
|
||||||
vm.WriteTrace(os.Stderr, debugLogger.StructLogs())
|
vm.WriteTrace(ctx.App.ErrWriter, debugLogger.StructLogs())
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "#### LOGS ####")
|
fmt.Fprintln(ctx.App.ErrWriter, "#### LOGS ####")
|
||||||
vm.WriteLogs(os.Stderr, statedb.Logs())
|
vm.WriteLogs(ctx.App.ErrWriter, statedb.Logs())
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.GlobalBool(StatDumpFlag.Name) {
|
if ctx.GlobalBool(StatDumpFlag.Name) {
|
||||||
var mem goruntime.MemStats
|
var mem goruntime.MemStats
|
||||||
goruntime.ReadMemStats(&mem)
|
goruntime.ReadMemStats(&mem)
|
||||||
fmt.Fprintf(os.Stderr, `evm execution time: %v
|
fmt.Fprintf(ctx.App.ErrWriter, `evm execution time: %v
|
||||||
heap objects: %d
|
heap objects: %d
|
||||||
allocations: %d
|
allocations: %d
|
||||||
total allocations: %d
|
total allocations: %d
|
||||||
|
|
@ -234,12 +237,10 @@ Gas used: %d
|
||||||
|
|
||||||
`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
|
`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas)
|
||||||
}
|
}
|
||||||
if tracer != nil {
|
if tracer == nil {
|
||||||
tracer.CaptureEnd(ret, initialGas-leftOverGas, execTime, err)
|
fmt.Fprintf(ctx.App.Writer, "0x%x\n", ret)
|
||||||
} else {
|
|
||||||
fmt.Printf("0x%x\n", ret)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" error: %v\n", err)
|
fmt.Fprintf(ctx.App.ErrWriter, " error: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ var stateTestCommand = cli.Command{
|
||||||
ArgsUsage: "<file>",
|
ArgsUsage: "<file>",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StatetestResult contains the execution status after running a state test, any
|
||||||
|
// error that might have occurred and a dump of the final state if requested.
|
||||||
type StatetestResult struct {
|
type StatetestResult struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Pass bool `json:"pass"`
|
Pass bool `json:"pass"`
|
||||||
|
|
@ -105,7 +107,7 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
// print state root for evmlab tracing (already committed above, so no need to delete objects again
|
// print state root for evmlab tracing (already committed above, so no need to delete objects again
|
||||||
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
|
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
|
||||||
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
|
fmt.Fprintf(ctx.App.ErrWriter, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, *result)
|
results = append(results, *result)
|
||||||
|
|
@ -113,13 +115,13 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||||
// Print any structured logs collected
|
// Print any structured logs collected
|
||||||
if ctx.GlobalBool(DebugFlag.Name) {
|
if ctx.GlobalBool(DebugFlag.Name) {
|
||||||
if debugger != nil {
|
if debugger != nil {
|
||||||
fmt.Fprintln(os.Stderr, "#### TRACE ####")
|
fmt.Fprintln(ctx.App.ErrWriter, "#### TRACE ####")
|
||||||
vm.WriteTrace(os.Stderr, debugger.StructLogs())
|
vm.WriteTrace(ctx.App.ErrWriter, debugger.StructLogs())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out, _ := json.MarshalIndent(results, "", " ")
|
out, _ := json.MarshalIndent(results, "", " ")
|
||||||
fmt.Println(string(out))
|
fmt.Fprintln(ctx.App.Writer, string(out))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,6 @@ var (
|
||||||
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
|
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
|
||||||
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
|
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
|
||||||
|
|
||||||
githubUser = flag.String("github.user", "", "GitHub user to authenticate with for Gist access")
|
|
||||||
githubToken = flag.String("github.token", "", "GitHub personal token to access Gists with")
|
|
||||||
|
|
||||||
captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
|
captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side")
|
||||||
captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
|
captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side")
|
||||||
|
|
||||||
|
|
@ -216,7 +213,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
||||||
// Assemble the raw devp2p protocol stack
|
// Assemble the raw devp2p protocol stack
|
||||||
stack, err := node.New(&node.Config{
|
stack, err := node.New(&node.Config{
|
||||||
Name: "geth",
|
Name: "geth",
|
||||||
Version: params.Version,
|
Version: params.VersionWithMeta,
|
||||||
DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
|
DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
|
||||||
P2P: p2p.Config{
|
P2P: p2p.Config{
|
||||||
NAT: nat.Any(),
|
NAT: nat.Any(),
|
||||||
|
|
@ -474,7 +471,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
|
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
|
||||||
|
|
||||||
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil)
|
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil)
|
||||||
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
if err = sendError(conn, err); err != nil {
|
if err = sendError(conn, err); err != nil {
|
||||||
|
|
@ -638,59 +635,6 @@ func sendSuccess(conn *websocket.Conn, msg string) error {
|
||||||
return send(conn, map[string]string{"success": msg}, time.Second)
|
return send(conn, map[string]string{"success": msg}, time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// authGitHub tries to authenticate a faucet request using GitHub gists, returning
|
|
||||||
// the username, avatar URL and Ethereum address to fund on success.
|
|
||||||
func authGitHub(url string) (string, string, common.Address, error) {
|
|
||||||
// Retrieve the gist from the GitHub Gist APIs
|
|
||||||
parts := strings.Split(url, "/")
|
|
||||||
req, _ := http.NewRequest("GET", "https://api.github.com/gists/"+parts[len(parts)-1], nil)
|
|
||||||
if *githubUser != "" {
|
|
||||||
req.SetBasicAuth(*githubUser, *githubToken)
|
|
||||||
}
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", common.Address{}, err
|
|
||||||
}
|
|
||||||
var gist struct {
|
|
||||||
Owner struct {
|
|
||||||
Login string `json:"login"`
|
|
||||||
} `json:"owner"`
|
|
||||||
Files map[string]struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
} `json:"files"`
|
|
||||||
}
|
|
||||||
err = json.NewDecoder(res.Body).Decode(&gist)
|
|
||||||
res.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return "", "", common.Address{}, err
|
|
||||||
}
|
|
||||||
if gist.Owner.Login == "" {
|
|
||||||
return "", "", common.Address{}, errors.New("Anonymous Gists not allowed")
|
|
||||||
}
|
|
||||||
// Iterate over all the files and look for Ethereum addresses
|
|
||||||
var address common.Address
|
|
||||||
for _, file := range gist.Files {
|
|
||||||
content := strings.TrimSpace(file.Content)
|
|
||||||
if len(content) == 2+common.AddressLength*2 {
|
|
||||||
address = common.HexToAddress(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if address == (common.Address{}) {
|
|
||||||
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
|
|
||||||
}
|
|
||||||
// Validate the user's existence since the API is unhelpful here
|
|
||||||
if res, err = http.Head("https://github.com/" + gist.Owner.Login); err != nil {
|
|
||||||
return "", "", common.Address{}, err
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return "", "", common.Address{}, errors.New("Invalid user... boom!")
|
|
||||||
}
|
|
||||||
// Everything passed validation, return the gathered infos
|
|
||||||
return gist.Owner.Login + "@github", fmt.Sprintf("https://github.com/%s.png?size=64", gist.Owner.Login), address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authTwitter tries to authenticate a faucet request using Twitter posts, returning
|
// authTwitter tries to authenticate a faucet request using Twitter posts, returning
|
||||||
// the username, avatar URL and Ethereum address to fund on success.
|
// the username, avatar URL and Ethereum address to fund on success.
|
||||||
func authTwitter(url string) (string, string, common.Address, error) {
|
func authTwitter(url string) (string, string, common.Address, error) {
|
||||||
|
|
|
||||||
|
|
@ -340,7 +340,7 @@ func importWallet(ctx *cli.Context) error {
|
||||||
if len(keyfile) == 0 {
|
if len(keyfile) == 0 {
|
||||||
utils.Fatalf("keyfile must be given as argument")
|
utils.Fatalf("keyfile must be given as argument")
|
||||||
}
|
}
|
||||||
keyJson, err := ioutil.ReadFile(keyfile)
|
keyJSON, err := ioutil.ReadFile(keyfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not read wallet file: %v", err)
|
utils.Fatalf("Could not read wallet file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -349,7 +349,7 @@ func importWallet(ctx *cli.Context) error {
|
||||||
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
|
passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
acct, err := ks.ImportPreSaleKey(keyJson, passphrase)
|
acct, err := ks.ImportPreSaleKey(keyJSON, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ var bugCommand = cli.Command{
|
||||||
Category: "MISCELLANEOUS COMMANDS",
|
Category: "MISCELLANEOUS COMMANDS",
|
||||||
}
|
}
|
||||||
|
|
||||||
const issueUrl = "https://github.com/ethereum/go-ethereum/issues/new"
|
const issueURL = "https://github.com/ethereum/go-ethereum/issues/new"
|
||||||
|
|
||||||
// reportBug reports a bug by opening a new URL to the go-ethereum GH issue
|
// reportBug reports a bug by opening a new URL to the go-ethereum GH issue
|
||||||
// tracker and setting default values as the issue body.
|
// tracker and setting default values as the issue body.
|
||||||
|
|
@ -49,15 +49,17 @@ func reportBug(ctx *cli.Context) error {
|
||||||
// execute template and write contents to buff
|
// execute template and write contents to buff
|
||||||
var buff bytes.Buffer
|
var buff bytes.Buffer
|
||||||
|
|
||||||
fmt.Fprintln(&buff, header)
|
fmt.Fprintln(&buff, "#### System information")
|
||||||
fmt.Fprintln(&buff, "Version:", params.Version)
|
fmt.Fprintln(&buff)
|
||||||
|
fmt.Fprintln(&buff, "Version:", params.VersionWithMeta)
|
||||||
fmt.Fprintln(&buff, "Go Version:", runtime.Version())
|
fmt.Fprintln(&buff, "Go Version:", runtime.Version())
|
||||||
fmt.Fprintln(&buff, "OS:", runtime.GOOS)
|
fmt.Fprintln(&buff, "OS:", runtime.GOOS)
|
||||||
printOSDetails(&buff)
|
printOSDetails(&buff)
|
||||||
|
fmt.Fprintln(&buff, header)
|
||||||
|
|
||||||
// open a new GH issue
|
// open a new GH issue
|
||||||
if !browser.Open(issueUrl + "?body=" + url.QueryEscape(buff.String())) {
|
if !browser.Open(issueURL + "?body=" + url.QueryEscape(buff.String())) {
|
||||||
fmt.Printf("Please file a new issue at %s using this template:\n%s", issueUrl, buff.String())
|
fmt.Printf("Please file a new issue at %s using this template:\n\n%s", issueURL, buff.String())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -97,13 +99,15 @@ func printCmdOut(w io.Writer, prefix, path string, args ...string) {
|
||||||
fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out))
|
fmt.Fprintf(w, "%s%s\n", prefix, bytes.TrimSpace(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = `Please answer these questions before submitting your issue. Thanks!
|
const header = `
|
||||||
|
#### Expected behaviour
|
||||||
|
|
||||||
#### What did you do?
|
|
||||||
|
|
||||||
#### What did you expect to see?
|
#### Actual behaviour
|
||||||
|
|
||||||
#### What did you see instead?
|
|
||||||
|
|
||||||
#### System details
|
#### Steps to reproduce the behaviour
|
||||||
|
|
||||||
|
|
||||||
|
#### Backtrace
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,8 @@ processing will proceed even if an individual RLP-file import failure occurs.`,
|
||||||
Requires a first argument of the file to write to.
|
Requires a first argument of the file to write to.
|
||||||
Optional second and third arguments control the first and
|
Optional second and third arguments control the first and
|
||||||
last block to write. In this mode, the file will be appended
|
last block to write. In this mode, the file will be appended
|
||||||
if already existing.`,
|
if already existing. If the file ends with .gz, the output will
|
||||||
|
be gzipped.`,
|
||||||
}
|
}
|
||||||
importPreimagesCommand = cli.Command{
|
importPreimagesCommand = cli.Command{
|
||||||
Action: utils.MigrateFlags(importPreimages),
|
Action: utils.MigrateFlags(importPreimages),
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func TestConsoleWelcome(t *testing.T) {
|
||||||
geth.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
geth.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||||
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
geth.SetTemplateFunc("gover", runtime.Version)
|
geth.SetTemplateFunc("gover", runtime.Version)
|
||||||
geth.SetTemplateFunc("gethver", func() string { return params.Version })
|
geth.SetTemplateFunc("gethver", func() string { return params.VersionWithMeta })
|
||||||
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||||
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
|
||||||
attach.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
attach.SetTemplateFunc("goos", func() string { return runtime.GOOS })
|
||||||
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
attach.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||||
attach.SetTemplateFunc("gover", runtime.Version)
|
attach.SetTemplateFunc("gover", runtime.Version)
|
||||||
attach.SetTemplateFunc("gethver", func() string { return params.Version })
|
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithMeta })
|
||||||
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
|
||||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
|
||||||
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
@ -131,8 +131,8 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc
|
||||||
if genesis != "" {
|
if genesis != "" {
|
||||||
genesisHash = daoGenesisHash
|
genesisHash = daoGenesisHash
|
||||||
}
|
}
|
||||||
config, err := core.GetChainConfig(db, genesisHash)
|
config := rawdb.ReadChainConfig(db, genesisHash)
|
||||||
if err != nil {
|
if config == nil {
|
||||||
t.Errorf("test %d: failed to retrieve chain config: %v", test, err)
|
t.Errorf("test %d: failed to retrieve chain config: %v", test, err)
|
||||||
return // we want to return here, the other checks can't make it past this point (nil panic).
|
return // we want to return here, the other checks can't make it past this point (nil panic).
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ var customGenesisTests = []struct {
|
||||||
"homesteadBlock" : 314,
|
"homesteadBlock" : 314,
|
||||||
"daoForkBlock" : 141,
|
"daoForkBlock" : 141,
|
||||||
"daoForkSupport" : true
|
"daoForkSupport" : true
|
||||||
},
|
}
|
||||||
}`,
|
}`,
|
||||||
query: "eth.getBlock(0).nonce",
|
query: "eth.getBlock(0).nonce",
|
||||||
result: "0x0000000000000042",
|
result: "0x0000000000000042",
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,19 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
godebug "runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/elastic/gosigar"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/console"
|
"github.com/ethereum/go-ethereum/console"
|
||||||
"github.com/ethereum/go-ethereum/eth"
|
"github.com/ethereum/go-ethereum/eth"
|
||||||
"github.com/ethereum/go-ethereum/ethclient"
|
"github.com/ethereum/go-ethereum/ethclient"
|
||||||
|
|
@ -46,8 +49,6 @@ const (
|
||||||
var (
|
var (
|
||||||
// Git SHA1 commit hash of the release (set via linker flags)
|
// Git SHA1 commit hash of the release (set via linker flags)
|
||||||
gitCommit = ""
|
gitCommit = ""
|
||||||
// Ethereum address of the Geth release oracle.
|
|
||||||
relOracle = common.HexToAddress("0xfa7b9770ca4cb04296cac84f37736d4041251cdf")
|
|
||||||
// The app that holds all commands and flags.
|
// The app that holds all commands and flags.
|
||||||
app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
|
app = utils.NewApp(gitCommit, "the go-ethereum command line interface")
|
||||||
// flags that configure the node
|
// flags that configure the node
|
||||||
|
|
@ -143,13 +144,22 @@ var (
|
||||||
utils.WhisperMaxMessageSizeFlag,
|
utils.WhisperMaxMessageSizeFlag,
|
||||||
utils.WhisperMinPOWFlag,
|
utils.WhisperMinPOWFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
metricsFlags = []cli.Flag{
|
||||||
|
utils.MetricsEnableInfluxDBFlag,
|
||||||
|
utils.MetricsInfluxDBEndpointFlag,
|
||||||
|
utils.MetricsInfluxDBDatabaseFlag,
|
||||||
|
utils.MetricsInfluxDBUsernameFlag,
|
||||||
|
utils.MetricsInfluxDBPasswordFlag,
|
||||||
|
utils.MetricsInfluxDBHostTagFlag,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Initialize the CLI app and start Geth
|
// Initialize the CLI app and start Geth
|
||||||
app.Action = geth
|
app.Action = geth
|
||||||
app.HideVersion = true // we have a command to print the version
|
app.HideVersion = true // we have a command to print the version
|
||||||
app.Copyright = "Copyright 2013-2017 The go-ethereum Authors"
|
app.Copyright = "Copyright 2013-2018 The go-ethereum Authors"
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
// See chaincmd.go:
|
// See chaincmd.go:
|
||||||
initCommand,
|
initCommand,
|
||||||
|
|
@ -185,12 +195,37 @@ func init() {
|
||||||
app.Flags = append(app.Flags, consoleFlags...)
|
app.Flags = append(app.Flags, consoleFlags...)
|
||||||
app.Flags = append(app.Flags, debug.Flags...)
|
app.Flags = append(app.Flags, debug.Flags...)
|
||||||
app.Flags = append(app.Flags, whisperFlags...)
|
app.Flags = append(app.Flags, whisperFlags...)
|
||||||
|
app.Flags = append(app.Flags, metricsFlags...)
|
||||||
|
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
if err := debug.Setup(ctx); err != nil {
|
|
||||||
|
logdir := ""
|
||||||
|
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||||
|
logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
|
||||||
|
}
|
||||||
|
if err := debug.Setup(ctx, logdir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Cap the cache allowance and tune the garbage collector
|
||||||
|
var mem gosigar.Mem
|
||||||
|
if err := mem.Get(); err == nil {
|
||||||
|
allowance := int(mem.Total / 1024 / 1024 / 3)
|
||||||
|
if cache := ctx.GlobalInt(utils.CacheFlag.Name); cache > allowance {
|
||||||
|
log.Warn("Sanitizing cache to Go's GC limits", "provided", cache, "updated", allowance)
|
||||||
|
ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(allowance))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ensure Go's GC ignores the database cache for trigger percentage
|
||||||
|
cache := ctx.GlobalInt(utils.CacheFlag.Name)
|
||||||
|
gogc := math.Max(20, math.Min(100, 100/(float64(cache)/1024)))
|
||||||
|
|
||||||
|
log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc))
|
||||||
|
godebug.SetGCPercent(int(gogc))
|
||||||
|
|
||||||
|
// Start metrics export if enabled
|
||||||
|
utils.SetupMetrics(ctx)
|
||||||
|
|
||||||
// Start system runtime metrics collection
|
// Start system runtime metrics collection
|
||||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||||
|
|
||||||
|
|
@ -216,6 +251,9 @@ func main() {
|
||||||
// It creates a default node based on the command line arguments and runs it in
|
// It creates a default node based on the command line arguments and runs it in
|
||||||
// blocking mode, waiting for it to be shut down.
|
// blocking mode, waiting for it to be shut down.
|
||||||
func geth(ctx *cli.Context) error {
|
func geth(ctx *cli.Context) error {
|
||||||
|
if args := ctx.Args(); len(args) > 0 {
|
||||||
|
return fmt.Errorf("invalid command: %q", args[0])
|
||||||
|
}
|
||||||
node := makeFullNode(ctx)
|
node := makeFullNode(ctx)
|
||||||
startNode(ctx, node)
|
startNode(ctx, node)
|
||||||
node.Wait()
|
node.Wait()
|
||||||
|
|
@ -226,6 +264,8 @@ func geth(ctx *cli.Context) error {
|
||||||
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
|
||||||
// miner.
|
// miner.
|
||||||
func startNode(ctx *cli.Context, stack *node.Node) {
|
func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
|
debug.Memsize.Add("node", stack)
|
||||||
|
|
||||||
// Start up the node itself
|
// Start up the node itself
|
||||||
utils.StartNode(stack)
|
utils.StartNode(stack)
|
||||||
|
|
||||||
|
|
@ -244,7 +284,7 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
stack.AccountManager().Subscribe(events)
|
stack.AccountManager().Subscribe(events)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
// Create an chain state reader for self-derivation
|
// Create a chain state reader for self-derivation
|
||||||
rpcClient, err := stack.Attach()
|
rpcClient, err := stack.Attach()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to attach to self: %v", err)
|
utils.Fatalf("Failed to attach to self: %v", err)
|
||||||
|
|
@ -268,11 +308,11 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
status, _ := event.Wallet.Status()
|
status, _ := event.Wallet.Status()
|
||||||
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
||||||
|
|
||||||
|
derivationPath := accounts.DefaultBaseDerivationPath
|
||||||
if event.Wallet.URL().Scheme == "ledger" {
|
if event.Wallet.URL().Scheme == "ledger" {
|
||||||
event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader)
|
derivationPath = accounts.DefaultLedgerBaseDerivationPath
|
||||||
} else {
|
|
||||||
event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
|
|
||||||
}
|
}
|
||||||
|
event.Wallet.SelfDerive(derivationPath, stateReader)
|
||||||
|
|
||||||
case accounts.WalletDropped:
|
case accounts.WalletDropped:
|
||||||
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ func makedag(ctx *cli.Context) error {
|
||||||
|
|
||||||
func version(ctx *cli.Context) error {
|
func version(ctx *cli.Context) error {
|
||||||
fmt.Println(strings.Title(clientIdentifier))
|
fmt.Println(strings.Title(clientIdentifier))
|
||||||
fmt.Println("Version:", params.Version)
|
fmt.Println("Version:", params.VersionWithMeta)
|
||||||
if gitCommit != "" {
|
if gitCommit != "" {
|
||||||
fmt.Println("Git Commit:", gitCommit)
|
fmt.Println("Git Commit:", gitCommit)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,12 +185,12 @@ func resolveMetric(metrics map[string]interface{}, pattern string, path string)
|
||||||
parts := strings.SplitN(pattern, "/", 2)
|
parts := strings.SplitN(pattern, "/", 2)
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
for _, variation := range strings.Split(parts[0], ",") {
|
for _, variation := range strings.Split(parts[0], ",") {
|
||||||
if submetrics, ok := metrics[variation].(map[string]interface{}); !ok {
|
submetrics, ok := metrics[variation].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
utils.Fatalf("Failed to retrieve system metrics: %s", path+variation)
|
utils.Fatalf("Failed to retrieve system metrics: %s", path+variation)
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
results = append(results, resolveMetric(submetrics, parts[1], path+variation+"/")...)
|
|
||||||
}
|
}
|
||||||
|
results = append(results, resolveMetric(submetrics, parts[1], path+variation+"/")...)
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import (
|
||||||
var AppHelpTemplate = `NAME:
|
var AppHelpTemplate = `NAME:
|
||||||
{{.App.Name}} - {{.App.Usage}}
|
{{.App.Name}} - {{.App.Usage}}
|
||||||
|
|
||||||
Copyright 2013-2017 The go-ethereum Authors
|
Copyright 2013-2018 The go-ethereum Authors
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
|
{{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}}
|
||||||
|
|
@ -83,7 +83,8 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.LightKDFFlag,
|
utils.LightKDFFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{Name: "DEVELOPER CHAIN",
|
{
|
||||||
|
Name: "DEVELOPER CHAIN",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DeveloperFlag,
|
utils.DeveloperFlag,
|
||||||
utils.DeveloperPeriodFlag,
|
utils.DeveloperPeriodFlag,
|
||||||
|
|
@ -206,11 +207,22 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
{
|
{
|
||||||
Name: "LOGGING AND DEBUGGING",
|
Name: "LOGGING AND DEBUGGING",
|
||||||
Flags: append([]cli.Flag{
|
Flags: append([]cli.Flag{
|
||||||
utils.MetricsEnabledFlag,
|
|
||||||
utils.FakePoWFlag,
|
utils.FakePoWFlag,
|
||||||
utils.NoCompactionFlag,
|
utils.NoCompactionFlag,
|
||||||
}, debug.Flags...),
|
}, debug.Flags...),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "METRICS AND STATS",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.MetricsEnabledFlag,
|
||||||
|
utils.MetricsEnableInfluxDBFlag,
|
||||||
|
utils.MetricsInfluxDBEndpointFlag,
|
||||||
|
utils.MetricsInfluxDBDatabaseFlag,
|
||||||
|
utils.MetricsInfluxDBUsernameFlag,
|
||||||
|
utils.MetricsInfluxDBPasswordFlag,
|
||||||
|
utils.MetricsInfluxDBHostTagFlag,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "WHISPER (EXPERIMENTAL)",
|
Name: "WHISPER (EXPERIMENTAL)",
|
||||||
Flags: whisperFlags,
|
Flags: whisperFlags,
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,10 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
app.Run(os.Args)
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showNetwork(ctx *cli.Context) error {
|
func showNetwork(ctx *cli.Context) error {
|
||||||
|
|
@ -275,9 +278,8 @@ func createNode(ctx *cli.Context) error {
|
||||||
if len(ctx.Args()) != 0 {
|
if len(ctx.Args()) != 0 {
|
||||||
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
||||||
}
|
}
|
||||||
config := &adapters.NodeConfig{
|
config := adapters.RandomNodeConfig()
|
||||||
Name: ctx.String("name"),
|
config.Name = ctx.String("name")
|
||||||
}
|
|
||||||
if key := ctx.String("key"); key != "" {
|
if key := ctx.String("key"); key != "" {
|
||||||
privKey, err := crypto.HexToECDSA(key)
|
privKey, err := crypto.HexToECDSA(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,8 @@ func newCppEthereumGenesisSpec(network string, genesis *core.Genesis) (*cppEther
|
||||||
spec.Params.ByzantiumForkBlock = (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64())
|
spec.Params.ByzantiumForkBlock = (hexutil.Uint64)(genesis.Config.ByzantiumBlock.Uint64())
|
||||||
spec.Params.ConstantinopleForkBlock = (hexutil.Uint64)(math.MaxUint64)
|
spec.Params.ConstantinopleForkBlock = (hexutil.Uint64)(math.MaxUint64)
|
||||||
|
|
||||||
spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainId.Uint64())
|
spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainID.Uint64())
|
||||||
spec.Params.ChainID = (hexutil.Uint64)(genesis.Config.ChainId.Uint64())
|
spec.Params.ChainID = (hexutil.Uint64)(genesis.Config.ChainID.Uint64())
|
||||||
|
|
||||||
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
|
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
|
||||||
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
|
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
|
||||||
|
|
@ -284,7 +284,7 @@ func newParityChainSpec(network string, genesis *core.Genesis, bootnodes []strin
|
||||||
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
|
spec.Params.MaximumExtraDataSize = (hexutil.Uint64)(params.MaximumExtraDataSize)
|
||||||
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
|
spec.Params.MinGasLimit = (hexutil.Uint64)(params.MinGasLimit)
|
||||||
spec.Params.GasLimitBoundDivisor = (hexutil.Uint64)(params.GasLimitBoundDivisor)
|
spec.Params.GasLimitBoundDivisor = (hexutil.Uint64)(params.GasLimitBoundDivisor)
|
||||||
spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainId.Uint64())
|
spec.Params.NetworkID = (hexutil.Uint64)(genesis.Config.ChainID.Uint64())
|
||||||
spec.Params.MaxCodeSize = params.MaxCodeSize
|
spec.Params.MaxCodeSize = params.MaxCodeSize
|
||||||
spec.Params.EIP155Transition = genesis.Config.EIP155Block.Uint64()
|
spec.Params.EIP155Transition = genesis.Config.EIP155Block.Uint64()
|
||||||
spec.Params.EIP98Transition = math.MaxUint64
|
spec.Params.EIP98Transition = math.MaxUint64
|
||||||
|
|
|
||||||
|
|
@ -609,7 +609,7 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
|
||||||
}
|
}
|
||||||
template.Must(template.New("").Parse(dashboardContent)).Execute(indexfile, map[string]interface{}{
|
template.Must(template.New("").Parse(dashboardContent)).Execute(indexfile, map[string]interface{}{
|
||||||
"Network": network,
|
"Network": network,
|
||||||
"NetworkID": conf.Genesis.Config.ChainId,
|
"NetworkID": conf.Genesis.Config.ChainID,
|
||||||
"NetworkTitle": strings.Title(network),
|
"NetworkTitle": strings.Title(network),
|
||||||
"EthstatsPage": config.ethstats,
|
"EthstatsPage": config.ethstats,
|
||||||
"ExplorerPage": config.explorer,
|
"ExplorerPage": config.explorer,
|
||||||
|
|
@ -683,7 +683,7 @@ func deployDashboard(client *sshClient, network string, conf *config, config *da
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dashboardInfos is returned from an dashboard status check to allow reporting
|
// dashboardInfos is returned from a dashboard status check to allow reporting
|
||||||
// various configuration parameters.
|
// various configuration parameters.
|
||||||
type dashboardInfos struct {
|
type dashboardInfos struct {
|
||||||
host string
|
host string
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ func (info *ethstatsInfos) Report() map[string]string {
|
||||||
"Website address": info.host,
|
"Website address": info.host,
|
||||||
"Website listener port": strconv.Itoa(info.port),
|
"Website listener port": strconv.Itoa(info.port),
|
||||||
"Login secret": info.secret,
|
"Login secret": info.secret,
|
||||||
"Banned addresses": fmt.Sprintf("%v", info.banned),
|
"Banned addresses": strings.Join(info.banned, "\n"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ func (info *explorerInfos) Report() map[string]string {
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkExplorer does a health-check against an block explorer server to verify
|
// checkExplorer does a health-check against a block explorer server to verify
|
||||||
// whether it's running, and if yes, whether it's responsive.
|
// whether it's running, and if yes, whether it's responsive.
|
||||||
func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
|
func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
|
||||||
// Inspect a possible block explorer container on the host
|
// Inspect a possible block explorer container on the host
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// faucetDockerfile is the Dockerfile required to build an faucet container to
|
// faucetDockerfile is the Dockerfile required to build a faucet container to
|
||||||
// grant crypto tokens based on GitHub authentications.
|
// grant crypto tokens based on GitHub authentications.
|
||||||
var faucetDockerfile = `
|
var faucetDockerfile = `
|
||||||
FROM ethereum/client-go:alltools-latest
|
FROM ethereum/client-go:alltools-latest
|
||||||
|
|
@ -138,7 +138,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
||||||
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate", workdir, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
// faucetInfos is returned from an faucet status check to allow reporting various
|
// faucetInfos is returned from a faucet status check to allow reporting various
|
||||||
// configuration parameters.
|
// configuration parameters.
|
||||||
type faucetInfos struct {
|
type faucetInfos struct {
|
||||||
node *nodeInfos
|
node *nodeInfos
|
||||||
|
|
@ -181,7 +181,7 @@ func (info *faucetInfos) Report() map[string]string {
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFaucet does a health-check against an faucet server to verify whether
|
// checkFaucet does a health-check against a faucet server to verify whether
|
||||||
// it's running, and if yes, gathering a collection of useful infos about it.
|
// it's running, and if yes, gathering a collection of useful infos about it.
|
||||||
func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
||||||
// Inspect a possible faucet container on the host
|
// Inspect a possible faucet container on the host
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ func (info *nodeInfos) Report() map[string]string {
|
||||||
return report
|
return report
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNode does a health-check against an boot or seal node server to verify
|
// checkNode does a health-check against a boot or seal node server to verify
|
||||||
// whether it's running, and if yes, whether it's responsive.
|
// whether it's running, and if yes, whether it's responsive.
|
||||||
func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) {
|
func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) {
|
||||||
kind := "bootnode"
|
kind := "bootnode"
|
||||||
|
|
@ -221,7 +221,7 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||||
|
|
||||||
// Container available, retrieve its node ID and its genesis json
|
// Container available, retrieve its node ID and its genesis json
|
||||||
var out []byte
|
var out []byte
|
||||||
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.id attach", network, kind)); err != nil {
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.id --cache=16 attach", network, kind)); err != nil {
|
||||||
return nil, ErrServiceUnreachable
|
return nil, ErrServiceUnreachable
|
||||||
}
|
}
|
||||||
id := bytes.Trim(bytes.TrimSpace(out), "\"")
|
id := bytes.Trim(bytes.TrimSpace(out), "\"")
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ func (w *wizard) deployFaucet() {
|
||||||
existed := err == nil
|
existed := err == nil
|
||||||
|
|
||||||
infos.node.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
|
infos.node.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
|
||||||
infos.node.network = w.conf.Genesis.Config.ChainId.Int64()
|
infos.node.network = w.conf.Genesis.Config.ChainID.Int64()
|
||||||
|
|
||||||
// Figure out which port to listen on
|
// Figure out which port to listen on
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue