diff --git a/.gitattributes b/.gitattributes index dfe0770424b2..0269fab9cba2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Auto detect text files and perform LF normalization * text=auto +*.sol linguist-language=Solidity diff --git a/.travis.yml b/.travis.yml index cade11700f42..194ca639698b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,34 +12,33 @@ matrix: - sudo chmod 666 /dev/fuse - sudo chown root:$USER /etc/fuse.conf - 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. - os: linux dist: trusty sudo: required - go: "1.10" + go: 1.10.x script: - sudo modprobe fuse - sudo chmod 666 /dev/fuse - sudo chown root:$USER /etc/fuse.conf - go run build/ci.go install - - go run build/ci.go test -coverage + - go run build/ci.go test -coverage $TEST_PACKAGES - os: osx - go: "1.10" + go: 1.10.x script: - unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703 - brew update - - brew install caskroom/cask/brew-cask - brew cask install osxfuse - 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 - os: linux dist: trusty - go: "1.10" + go: 1.10.x env: - lint git: @@ -47,14 +46,12 @@ matrix: script: - 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 dist: trusty - sudo: required - go: "1.10" + go: 1.10.x env: - ubuntu-ppa - - azure-linux git: submodules: false # avoid cloning ethereum/tests addons: @@ -63,11 +60,25 @@ matrix: - devscripts - debhelper - dput - - gcc-multilib - fakeroot script: - # Build for the primary platforms that Trusty can manage - go run build/ci.go debsrc -signer "Go Ethereum Linux Builder " -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 archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds - go run build/ci.go install -arch 386 @@ -91,7 +102,7 @@ matrix: dist: trusty services: - docker - go: "1.10" + go: 1.10.x env: - azure-linux-mips git: @@ -135,7 +146,7 @@ matrix: git: submodules: false # avoid cloning ethereum/tests before_install: - - curl https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz | tar -xz + - curl https://storage.googleapis.com/golang/go1.10.1.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go @@ -152,7 +163,7 @@ matrix: # This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads - os: osx - go: "1.10" + go: 1.10.x env: - azure-osx - azure-ios @@ -181,8 +192,7 @@ matrix: # This builder does the Azure archive purges to avoid accumulating junk - os: linux dist: trusty - sudo: required - go: "1.10" + go: 1.10.x env: - azure-purge git: diff --git a/Dockerfile b/Dockerfile index 29cdc80f967a..edf5a0602d0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,5 +12,5 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates 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"] diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 1047738d25ac..e54e107bf39c 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -12,4 +12,4 @@ FROM alpine:latest RUN apk add --no-cache ca-certificates COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/ -EXPOSE 8545 8546 30303 30303/udp 30304/udp +EXPOSE 8545 8546 30303 30303/udp diff --git a/Makefile b/Makefile index 3922d6015bb9..5cb9231a1871 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,9 @@ ios: test: all build/env.sh go run build/ci.go test +lint: ## Run linters. + build/env.sh go run build/ci.go lint + clean: rm -fr build/_workspace/pkg/ $(GOBIN)/* diff --git a/README.md b/README.md index 3d0d4d35d410..b2e992a4ef4f 100644 --- a/README.md +++ b/README.md @@ -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 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 [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). diff --git a/VERSION b/VERSION index a7ee35a3ea70..88d3ee7900c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.3 +1.8.7 diff --git a/accounts/abi/event.go b/accounts/abi/event.go index 595f169f3a9a..a3f6be97323e 100644 --- a/accounts/abi/event.go +++ b/accounts/abi/event.go @@ -33,15 +33,15 @@ type Event struct { Inputs Arguments } -func (event Event) String() string { - inputs := make([]string, len(event.Inputs)) - for i, input := range event.Inputs { +func (e Event) String() string { + inputs := make([]string, len(e.Inputs)) + for i, input := range e.Inputs { inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type) if input.Indexed { inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type) } } - return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", ")) + return fmt.Sprintf("e %v(%v)", e.Name, strings.Join(inputs, ", ")) } // Id returns the canonical representation of the event's signature used by the diff --git a/accounts/abi/numbers.go b/accounts/abi/numbers.go index 9ad99f90d29f..0cd97cc66f36 100644 --- a/accounts/abi/numbers.go +++ b/accounts/abi/numbers.go @@ -25,23 +25,23 @@ import ( ) var ( - big_t = reflect.TypeOf(&big.Int{}) - derefbig_t = reflect.TypeOf(big.Int{}) - uint8_t = reflect.TypeOf(uint8(0)) - uint16_t = reflect.TypeOf(uint16(0)) - uint32_t = reflect.TypeOf(uint32(0)) - uint64_t = reflect.TypeOf(uint64(0)) - int_t = reflect.TypeOf(int(0)) - int8_t = reflect.TypeOf(int8(0)) - int16_t = reflect.TypeOf(int16(0)) - int32_t = reflect.TypeOf(int32(0)) - int64_t = reflect.TypeOf(int64(0)) - 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)) + bigT = reflect.TypeOf(&big.Int{}) + derefbigT = reflect.TypeOf(big.Int{}) + uint8T = reflect.TypeOf(uint8(0)) + uint16T = reflect.TypeOf(uint16(0)) + uint32T = reflect.TypeOf(uint32(0)) + uint64T = reflect.TypeOf(uint64(0)) + intT = reflect.TypeOf(int(0)) + int8T = reflect.TypeOf(int8(0)) + int16T = reflect.TypeOf(int16(0)) + int32T = reflect.TypeOf(int32(0)) + int64T = reflect.TypeOf(int64(0)) + addressT = reflect.TypeOf(common.Address{}) + intTS = reflect.TypeOf([]int(nil)) + int8TS = reflect.TypeOf([]int8(nil)) + int16TS = reflect.TypeOf([]int16(nil)) + int32TS = reflect.TypeOf([]int32(nil)) + int64TS = reflect.TypeOf([]int64(nil)) ) // U256 converts a big Int into a 256bit EVM number. @@ -52,7 +52,7 @@ func U256(n *big.Int) []byte { // 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: + case intTS, int8TS, int16TS, int32TS, int64TS, intT, int8T, int16T, int32T, int64T: return true } return false diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 2e6bf7098f2c..5620a7084517 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -24,7 +24,7 @@ import ( // indirect recursively dereferences the value until it either gets the value // or finds a big.Int func indirect(v reflect.Value) reflect.Value { - if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t { + if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT { return indirect(v.Elem()) } return v @@ -36,26 +36,26 @@ func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) switch size { case 8: if unsigned { - return reflect.Uint8, uint8_t + return reflect.Uint8, uint8T } - return reflect.Int8, int8_t + return reflect.Int8, int8T case 16: if unsigned { - return reflect.Uint16, uint16_t + return reflect.Uint16, uint16T } - return reflect.Int16, int16_t + return reflect.Int16, int16T case 32: if unsigned { - return reflect.Uint32, uint32_t + return reflect.Uint32, uint32T } - return reflect.Int32, int32_t + return reflect.Int32, int32T case 64: 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 diff --git a/accounts/abi/type.go b/accounts/abi/type.go index a1f13ffa29f3..9de36daffb69 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -135,7 +135,7 @@ func NewType(t string) (typ Type, err error) { typ.Type = reflect.TypeOf(bool(false)) case "address": typ.Kind = reflect.Array - typ.Type = address_t + typ.Type = addressT typ.Size = 20 typ.T = AddressTy case "string": diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index e55af1293951..f6b36f18fd54 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -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[][][]", 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][]"}}, - {"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}}, - {"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}}, - {"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}}, - {"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}}, - {"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}}, - {"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[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]"}}, - {"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[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]"}}, - {"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[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]"}}, - {"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[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]"}}, - {"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[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]"}}, - {"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}}, - {"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}}, - {"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}}, - {"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}}, - {"uint256", Type{Kind: reflect.Ptr, Type: big_t, 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[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]"}}, - {"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[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]"}}, - {"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[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]"}}, - {"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[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]"}}, - {"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[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]"}}, + {"int8", Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}}, + {"int16", Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}}, + {"int32", Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}}, + {"int64", Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}}, + {"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: 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: 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: 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: 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: 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: 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: 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: 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: 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: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}}, + {"uint8", Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}}, + {"uint16", Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}}, + {"uint32", Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}}, + {"uint64", Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}}, + {"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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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"}}, {"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]"}}, @@ -84,9 +84,9 @@ func TestTypeRegexp(t *testing.T) { {"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[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{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[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", 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: 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: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}}, // TODO when fixed types are implemented properly // {"fixed", Type{}}, // {"fixed128x128", Type{}}, @@ -252,6 +252,9 @@ func TestTypeCheck(t *testing.T) { {"bytes20", common.Address{}, ""}, {"address", [20]byte{}, ""}, {"address", common.Address{}, ""}, + {"bytes32[]]", "", "invalid arg type in abi"}, + {"invalidType", "", "unsupported arg type: invalidType"}, + {"invalidSlice[]", "", "unsupported arg type: invalidSlice"}, } { typ, err := NewType(test.typ) if err != nil && len(test.err) == 0 { diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index ee62567094a8..bdbab10b4f43 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -56,6 +56,23 @@ var unpackTests = []unpackTest{ enc: "0000000000000000000000000000000000000000000000000000000000000001", 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"}]`, enc: "0000000000000000000000000000000000000000000000000000000000000001", diff --git a/accounts/url.go b/accounts/url.go index 47f9d8ee4b20..21df668efd26 100644 --- a/accounts/url.go +++ b/accounts/url.go @@ -74,6 +74,22 @@ func (u URL) MarshalJSON() ([]byte, error) { 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: // // -1 if x < y diff --git a/accounts/usbwallet/hub.go b/accounts/usbwallet/hub.go index 61fc98ccc80e..640320bc9143 100644 --- a/accounts/usbwallet/hub.go +++ b/accounts/usbwallet/hub.go @@ -127,7 +127,7 @@ func (hub *Hub) refreshWallets() { // 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 // 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. hub.commsLock.Lock() if hub.commsPend > 0 { // A confirmation is pending, don't refresh diff --git a/accounts/usbwallet/ledger.go b/accounts/usbwallet/ledger.go index f5def61d235c..7ad32dd1e847 100644 --- a/accounts/usbwallet/ledger.go +++ b/accounts/usbwallet/ledger.go @@ -53,11 +53,9 @@ const ( ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration 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 ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing 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 diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 8b3b5a522402..6cef6e0fb02a 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -99,7 +99,7 @@ type wallet struct { // // 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 - // 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 // however should allow "skipping" waiting for operations that might want to // use the device, but can live without too (e.g. account self-derivation). diff --git a/appveyor.yml b/appveyor.yml index 45475d166947..141ef16ff824 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,8 +23,8 @@ environment: install: - git submodule update --init - rmdir C:\go /s /q - - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.windows-%GETH_ARCH%.zip - - 7z x go1.10.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://storage.googleapis.com/golang/go1.10.1.windows-%GETH_ARCH%.zip + - 7z x go1.10.1.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/bmt/bmt.go b/bmt/bmt.go index 4b65b1d94aac..3408758675f4 100644 --- a/bmt/bmt.go +++ b/bmt/bmt.go @@ -75,7 +75,7 @@ type Hasher struct { 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 + cur int // cursor position for rightmost currently open chunk segment []byte // the rightmost open segment (not complete) depth int // index of last level result chan []byte // result channel @@ -149,7 +149,7 @@ func NewTreePool(hasher BaseHasher, segmentCount, capacity int) *TreePool { } } -// Drain drains the pool uptil it has no more than n resources +// Drain drains the pool until it has no more than n resources func (self *TreePool) Drain(n int) { self.lock.Lock() defer self.lock.Unlock() @@ -412,11 +412,10 @@ func (self *Hasher) Reset() { // 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 +// the length 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 @@ -531,7 +530,7 @@ func (self *Hasher) finalise(n *Node, i int) (d int) { 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 + // we do not need toggle the state since this condition is // detectable n.unbalanced = isLeft n.right = nil diff --git a/build/ci.go b/build/ci.go index 1881a596e9ad..204c2067528b 100644 --- a/build/ci.go +++ b/build/ci.go @@ -329,7 +329,10 @@ func doLint(cmdline []string) { // Run fast linters batched together configs := []string{ "--vendor", + "--tests", "--disable-all", + "--enable=goimports", + "--enable=varcheck", "--enable=vet", "--enable=gofmt", "--enable=misspell", @@ -340,7 +343,7 @@ func doLint(cmdline []string) { // Run slow linters one by one 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...)...) } } @@ -728,7 +731,7 @@ func doAndroidArchive(cmdline []string) { // 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(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 we're building locally, copy bundle to build dir and skip Maven @@ -766,7 +769,7 @@ func doAndroidArchive(cmdline []string) { if meta.Develop { 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", "-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar") } @@ -849,7 +852,7 @@ func doXCodeFramework(cmdline []string) { // Build the iOS XCode framework build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile", "golang.org/x/mobile/cmd/gobind")) 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 we're building locally, use the build folder and stop afterwards diff --git a/build/goimports.sh b/build/goimports.sh new file mode 100755 index 000000000000..6d67ef1f0f68 --- /dev/null +++ b/build/goimports.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +find_files() { + find . -not \( \ + \( \ + -wholename '.github' \ + -o -wholename './build/_workspace' \ + -o -wholename './build/bin' \ + -o -wholename './crypto/bn256' \ + -o -wholename '*/vendor/*' \ + \) -prune \ + \) -name '*.go' +} + +GOFMT="gofmt -s -w"; +GOIMPORTS="goimports -w"; +find_files | xargs $GOFMT; +find_files | xargs $GOIMPORTS; \ No newline at end of file diff --git a/cmd/clef/4byte.json b/cmd/clef/4byte.json new file mode 100644 index 000000000000..5603d5931d78 --- /dev/null +++ b/cmd/clef/4byte.json @@ -0,0 +1 @@ +{"0x22ec1244": "shaBid(bytes32,address,uint256,bytes32)", "0xcae9ca51": "approveAndCall(address,uint256,bytes)", "0x4fb2e45d": "transferOwner(address)", "0x7741b4ec": "RandomNumberFromSeed(uint256)", "0x267127ec": "getTokenSettings()", "0xb7213bd4": "readLog(uint256)", "0x3018205f": "getController()", "0xc8edf65e": "GetAndReduceFeesByFraction(uint256)", "0xeec2b628": "beforeExecute(address)", "0xfc0c546a": "token()", "0x40a3d246": "toggle()", "0x70983e91": "startBoardProposal(uint256,address)", "0x6b5caec4": "setBot(address)", "0x78524b2e": "halveMinQuorum()", "0x2c60a055": "MapTest()", "0xc2fb8f36": "TinyHuman(address,address,address)", "0x6822abae": "getMinimumCallCost(uint256)", "0x6f9fb98a": "getContractBalance()", "0x5c17f9f4": "approve(address,uint256,bytes)", "0x504ac982": "transfer(string,string)", "0x06e53f47": "whichChainIsThis()", "0xf359671c": "withdrawWithReference(address,uint256,string)", "0xf97d0591": "parseTimestamp(uint256)", "0xd3c0715b": "vote(uint256,bool,string)", "0x6b069710": "scheduleCall(address,bytes,uint256,uint256,uint8)", "0x37ae43a3": "BetOnHashV81()", "0xab519020": "calcShare(uint256,uint256)", "0x6572ae13": "calculateWinner(uint256,uint256)", "0x6aaba012": "ErrorGenerator()", "0xfe05e8b1": "assertFact(uint256,string)", "0x6e940a29": "changeHost(address)", "0x669ee827": "RegisterDevice()", "0x6f4dd69c": "testSetBalanceUpdatesSupply()", "0x4401a6e4": "safeSend(address)", "0x27dc297e": "__callback(bytes32,string)", "0xe4dedc7f": "DeleteContract()", "0x7fef036e": "totalEntries()", "0x64325ddb": "currentClaimPrice()", "0x2fc0aad3": "isNumericString(string)", "0xbc45d789": "setConfigUint(int256,bytes32,uint256)", "0xee95feaf": "isSeller(address)", "0x358d5dc2": "getIsCashed(uint256,uint256)", "0x1397fdbd": "getShares(address,bytes,int256[])", "0x2d8c1c35": "level_up()", "0x24600fc3": "withdrawFunds()", "0x05f8b6f5": "_rewireIdentities(bytes32[],uint256,uint256,uint32)", "0x1840f0ca": "countVotes(uint256)", "0xd44aadf7": "initROS()", "0xca1d209d": "fund(uint256)", "0x5fa513d5": "findPtr(uint256,uint256,uint256,uint256)", "0x3c314a91": "playerGetPendingTxByAddress(address)", "0xd5582205": "getCertifiedStudentAtIndex(uint256)", "0xe45ebe93": "checkVoteStatus()", "0xcd9380d5": "testSetBalanceSetsSupplyCumulatively()", "0x637e86eb": "totBOTs()", "0x5bb47808": "setFactory(address)", "0x674cc1f5": "getMarketHashes(bytes32[])", "0x648bf774": "recover(address,address)", "0x0221038a": "payOut(address,uint256)", "0x4016535a": "parseBlock(bytes,uint256)", "0xa3908e1b": "convert(uint256)", "0xd9e7ee1c": "new_game(uint256,uint256)", "0x929e626e": "getShareDistribution(bytes32)", "0xa20495d3": "Managed()", "0xd409ddda": "EtherizationUtils()", "0xcb2b9031": "addressToBytes(address,address)", "0xfff3c457": "readMessages(uint256)", "0x043753ba": "makeDecision(uint256,bool)", "0x85b4bb53": "getSettings()", "0x60726abb": "copy()", "0xe50d0473": "SetRank(uint8,address,uint16)", "0x54ae8492": "CustodialForward()", "0xd6d02c51": "whois(string)", "0xcb712535": "_transferFrom(address,address,uint256)", "0xb152f19e": "getFirstSchedulableBlock()", "0x9334ab61": "Infos()", "0x88a49164": "testErrorUnauthorizedTransfer()", "0x17db59a4": "dEthereumlotteryNet(address,address,address)", "0xf85aefba": "testBitsSetFailIndexOOB()", "0xae99847b": "daylimit(uint256)", "0xd93e7573": "disown(bytes32)", "0xa5468081": "Pyramid(address)", "0x00e7d289": "registerListening(address)", "0x57ee24af": "getNum(bytes32,uint256)", "0xdaea85c5": "approve(address)", "0x36ffa905": "getMyProposals()", "0x7143059f": "getParticipant(address)", "0x55ff440a": "castStringToUInt(string)", "0x6a4a6b6e": "_myAddressHelper()", "0xb67fabdf": "scheduleTransaction(address,uint256,uint256,bytes)", "0xbcca1fd3": "changeVotingRules(uint256,uint256,int256)", "0x1d3390a1": "carefulSendWithFixedGas(address,uint256,uint256)", "0x45104b16": "EXECUTION_GAS_OVERHEAD()", "0xa26759cb": "addFunds()", "0x232523e8": "forceDivestOfAllInvestors()", "0x7e904a48": "getNumContents(uint256)", "0xb69c0896": "BaseScheduler(address,address,uint256)", "0xc6ed8e1b": "getApprovedProxys()", "0x4d1f8c31": "owner(uint64)", "0x17c65aa7": "getMaxLossAfterTrade(address,uint256,int256,int256)", "0x2c02d622": "precalculate()", "0xa035b1fe": "price()", "0x43b0e8df": "set(uint256,uint256,uint256)", "0x9b5fde7d": "payOut(uint256,string)", "0x89fcd099": "getApproval(address,address)", "0x4c0eceb5": "plusOnePonzi()", "0x880cdc31": "updateOwner(address)", "0xdab80d6f": "addSponsor(address)", "0x0fcda174": "getAccountTokenBalance(address,address)", "0xa55cab95": "getName(uint8,uint8)", "0x934db458": "Big()", "0xeb782d8c": "ContentSeries(address)", "0xdbfef710": "getDefaultRequiredGas()", "0x4f09eba7": "proxyApprove(address,uint256,bytes32)", "0xf4c5ab7c": "validateCallGas(uint256,uint256)", "0x376fe102": "userId(address)", "0x922dd59a": "icapTransfer(bytes,address,bytes,uint256)", "0x7318b453": "setVotetUntil(uint8)", "0xb8c86aa6": "getArraySettingResult()", "0x37bdc99b": "release(uint256)", "0x7cbcc254": "__reset__()", "0x37664643": "retractLatestRevision(bytes32)", "0x4b031d0f": "shortSellShares(bytes,uint8,uint256,uint256)", "0xad8d5f48": "exec(address,bytes,uint256)", "0x2f95b833": "requiredStackDepth()", "0xe3848e5b": "thing(string,string,string)", "0xaa272d4b": "getNodeIndexId(bytes)", "0xd7f746ce": "tickingBomb()", "0x3b84edbd": "setRNG(address)", "0x1fb2f2a0": "testUpdateLatestRevision()", "0xb7fba4d3": "getProxy(address)", "0x4b8e1ba8": "isMinter(int256,address)", "0xba4c206e": "removeCertificationDocumentInternal(address,bytes32)", "0x884b5dc2": "fill(uint256[])", "0x88017e05": "setContribution(uint256)", "0x1ff517ff": "totalDebt(address)", "0xd0315658": "getShareDistributionWithTimestamp(bytes)", "0x7d03f5f3": "newGame()", "0xb7538f3e": "ChangeClient(address)", "0xbf4d89b5": "parseInt(string,uint256)", "0x7b55c8b5": "scheduleCall(address,bytes4,bytes,uint8,uint256[4])", "0x350d141e": "getWasApprovedBeforeDeadline()", "0x27960c5f": "validateEndowment(uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0xb774d3d7": "BankOwner_GetDonationsBalance()", "0x267b6922": "entries(bytes32)", "0x08b7c13b": "getExists(bytes20)", "0x7e7a2fbf": "contribute_toTheGame()", "0x5b86914d": "bet_value()", "0x0e1087c3": "getMarketMakerFunds()", "0xf7149220": "RNG()", "0x345006b6": "getGenerationForCall(address)", "0xc4b14e0b": "getSignature(bytes32)", "0x419945f8": "ExpiringMarket(uint256)", "0x41868769": "CallAborted(address,bytes)", "0x29092d0e": "remove(address)", "0x746c9171": "m_required()", "0x5020dcf4": "convertToEach(uint256,string,uint256)", "0xa06db7dc": "gracePeriod()", "0xbf8fc670": "sendToAggregation(uint256)", "0xf14fcbc8": "commit(bytes32)", "0xa538d287": "getMinMax()", "0xcae523c1": "testOwnedTryAuthUnauthorized()", "0x04d10f1c": "isValidChainyJson(string)", "0x9ba5b4e9": "getEventHashes(bytes32[])", "0xfedfd535": "Config()", "0x42ce0f30": "testThrowUpdateLatestRevisionNotOwner()", "0x31be6985": "testBitXorSuccess()", "0x173cb7de": "getNumReleasesForNameHash(bytes32)", "0xd90a88cd": "getContentReplies(uint256,uint256)", "0x92eefe9b": "setController(address)", "0x052b81c7": "releaseBadges()", "0xb2855b4f": "setFeeAddr(address)", "0x19a9c2f1": "generateId(string)", "0xfa9acb05": "addressInArray(address,address)", "0x3da5c3ce": "puzzle(address,bytes32)", "0x7a427d98": "forceReturn()", "0x70e71ea3": "etherandomSeedWithGasLimit(uint256)", "0xd7a58658": "changeHouseedge(uint8)", "0x72b75585": "getOriginalClient()", "0xf802075f": "requiredEndowment()", "0x7997b997": "doMelt(uint256,uint256)", "0x6d5433e6": "max(uint256,uint256)", "0xb651cbaf": "add_level(address,bytes)", "0xb4d6d4c7": "getPackageData(bytes32)", "0x90e3c278": "getShares(uint256[128])", "0x179b73da": "killBoardProposal(uint256,address)", "0xc944a38e": "CharlyLifeLog(string,int256)", "0xe1c66292": "Create(uint32,address)", "0x69c8b344": "ownedToken(address)", "0xabcf1328": "InterestBank()", "0x532e7e6a": "calcEarningsSelling(bytes,uint256,uint256[],uint8,uint256)", "0x43d24a5e": "addUpdater(address)", "0xd1feca67": "addSpendingRequest(address)", "0x2d34ba79": "setup(address,address)", "0xcb14d93b": "getHash(bytes,address,uint256)", "0x309424fe": "get_all_names()", "0x96c52fc3": "____forward(address,uint256,uint256,bytes)", "0xde39acea": "get32(bytes,uint256)", "0xf3dd3d8a": "newCurrency(string,string,uint8)", "0x2432eb23": "testThrowRetractLatestRevisionNotUpdatable()", "0x7fcf3a2f": "throwFooBar()", "0xabe9f569": "oraclize_getPrice(string,uint256)", "0x41ee903e": "clear(uint256,uint256)", "0xd249a52e": "update(bytes,uint256[],uint256[])", "0xc3d014d6": "setContent(bytes32,bytes32)", "0x3ac5cb73": "GeometricPonzi()", "0x4a1aa767": "claim_victory(uint256,uint8,uint8,uint8)", "0xce592586": "setThresold(uint256,uint256)", "0x63deb2c5": "changeMemberAddress(address)", "0x2e6e504a": "trusteeWithdraw()", "0xcfed9199": "timePassed(uint256)", "0xb782fc9b": "getFirstActiveDuel2()", "0x35b28153": "addAuthorization(address)", "0x46f7a883": "BuyTicket(uint8,uint8,uint8)", "0x83c51a38": "thesimplegame()", "0xfa28ba0d": "validateReleaseLockfileURI(string)", "0xa7b2d4cb": "remove(int256,address)", "0x010731c0": "sendCryptedHand(bytes32)", "0xe9e99d81": "getChannelFeed(address,uint256,uint256,uint256)", "0x4e30a66c": "safeToAdd(uint256,uint256)", "0x2c4e591b": "totalGames()", "0xa3221c8e": "step8()", "0x783ce458": "expmod(uint256,uint256,uint256)", "0xe417291b": "undelegateDAOTokens(uint256)", "0x8e5d97a2": "releasePendingTransfer(uint256)", "0xbc5ff5e1": "oraclize_query(string,string[4],uint256)", "0x38f77d69": "getDistributeProfitsInfo()", "0xbb510a77": "createChannel(address,uint256)", "0x650955d4": "HashToken()", "0xa8484938": "doApprove(address,uint256)", "0x64ed31fe": "authVotes(address)", "0xf7ae9421": "checkInvestorBalance(address)", "0xba904eed": "removeRegistrar(address)", "0xdce4a447": "at(address)", "0xdb4cacec": "Other()", "0x3647b87a": "buildFactory()", "0xa51aea2d": "changeMaxMultiplier(uint256)", "0x4974bc27": "download()", "0xf8a8fd6d": "test()", "0xd8c90762": "addTrustedIssuer(address,string)", "0xdf6c13c3": "getMinFunding()", "0x867904b4": "issue(address,uint256)", "0x1531c267": "fipsRegisterMulti(uint256,address,bytes)", "0x40a49a96": "searchSmallestInvestor()", "0x61bffe01": "addIdentities(bytes32[],bytes32[])", "0xf77a0923": "BitcoinProcessor(address)", "0xd02528e6": "GetGameIndexesToProcess()", "0x9f927be7": "getNextCall(uint256)", "0xd8162db7": "lockedUntilBlock()", "0x36dfe260": "payOneTimeReward()", "0xc5b1a53c": "deposit(bytes16[],uint64)", "0xc2b6b58c": "isClosed()", "0xc88cc6ac": "getCertification(address)", "0x77ac3da5": "oraclize_query(uint256,string,string[1],uint256)", "0x70ab8ba8": "creditUpdate()", "0xd3ea3322": "testBuildTokenSystemCost()", "0x72388610": "paybackAll()", "0xca6d56dc": "addMember(address)", "0x0994a0a0": "DSTokenTest()", "0xe53e04a5": "refillGas()", "0xc1d5e84f": "addNewUser(address)", "0x89ed0b30": "setOraclizeGas(uint32)", "0x02ba8742": "sendCoins(address,uint256)", "0xb0de1cb7": "publish(uint64,bytes,uint64)", "0x0e13b9af": "getValue(uint8,uint8)", "0xb3dfcdc3": "Contribution(uint256)", "0xa9b35240": "packageExists(bytes32)", "0xd1d3bb92": "testSetPkg()", "0x97297467": "checkAndVerify(bytes)", "0xe31bfa00": "next_id()", "0x9948e493": "calcMarketFee(bytes,uint256)", "0xd148288f": "setHoldingPeriod(uint256)", "0xc032dc30": "execute(uint256,address)", "0xdad99989": "burnCoins(address)", "0xb1999937": "leapYearsBefore(uint256)", "0xa6cbcdd5": "numSignatures(bytes4)", "0xaca66aec": "DVIP()", "0x20bf0c52": "Derived(uint256)", "0x693ec85e": "get(string)", "0x0411bca8": "getChallengeAnswerResult(uint256)", "0x61584936": "sealedBids(bytes32)", "0x2f1927cb": "prepareRoll(uint256,uint256,uint256)", "0xeaa1f9fe": "reqisterListening(address)", "0xb623f5e5": "checkSetCosignerAddress(address)", "0xa88c5ef7": "NextPayout()", "0x66ad484c": "newfirst_player(address)", "0xb4022950": "collectFeesInEther(uint256)", "0xbff0fbb8": "calculateMeat(uint256)", "0xd62b255b": "setOwner(address,string)", "0x2fd6d40b": "getBetValueByGamble(uint8)", "0x3b0506f7": "getVoteByAddress(address,uint256)", "0xbddd3a6b": "step7()", "0x67fbd289": "destroyTokens(uint256)", "0x9844347b": "createCertificate(bytes,bytes,uint256,bytes)", "0x5e68ac2c": "Kingdom(string,address,address,address,uint256,uint256,uint256,uint256,uint256)", "0x8ba9f354": "testClearBitSuccess()", "0x48027610": "transferPaidOut(address,address,uint256)", "0x912de8de": "fixBalance()", "0x04509918": "scheduleCall(address)", "0x7cf0ffcb": "forceDivestAll()", "0x3b3b57de": "addr(bytes32)", "0xeb7c6f72": "step6()", "0xfe6f0d82": "testConstructorEvent()", "0x55b62dcf": "getThresold(uint256)", "0xfbae5e7d": "Investors(uint256)", "0x29e206bd": "forceDivestAll(bool)", "0x6a226a49": "addMessage(string)", "0x8e2a6470": "allocateShares(address,uint256)", "0xe6e7237f": "claim_time_victory(uint256)", "0x17a601b5": "MAX_STACK_DEPTH_REQUIREMENT()", "0x87fd0421": "TheEthereumLottery()", "0xc17e6817": "sendSafe(address,uint256)", "0xa5dfee67": "testThrowsCreateNewRevisionNotUpdatable()", "0xb35893f3": "setExporter()", "0x1ceea715": "GetMyInvestFee()", "0xb78bd4a5": "breakCookie(string)", "0x05215b2f": "createStandardToken(uint256)", "0x2632bf20": "unblockMe()", "0x5292af1f": "sendBalance(address)", "0xc2e9fab3": "SubUser()", "0x6493d7fc": "CircuitBreaker(address,address,uint256,uint256)", "0x4f896d4f": "resolve(uint256)", "0x16870257": "getTileDescription(uint8,uint8)", "0x3ef87414": "getRevisionCount(bytes20)", "0x747586b8": "setInt(int256)", "0x5714f6a1": "getTotalAvailableRelays()", "0x99154b49": "ARK()", "0x1efb17ee": "changeHouseAddress(address)", "0x354d7e40": "Payout()", "0x2da0d1ea": "etherSold()", "0xea46193e": "getEtherBalance()", "0x11fe773d": "memcpy(uint256,uint256,uint256)", "0x1e701780": "MICRODAO(address,uint256,uint256,uint256,address)", "0x1c31f710": "setBeneficiary(address)", "0x0a4caed0": "getChannelByRank(address,uint256)", "0xf3125a1f": "deposit(address,uint256,bytes,uint256)", "0x00e46700": "setMinimumPassPercentage(uint8)", "0x92d282c1": "Send()", "0x89d59ee5": "createPersonalDepositAddress()", "0xbe1c766b": "getLength()", "0x70a08231": "balanceOf(address)", "0xae0a6b28": "signature(string,bytes32)", "0xb3485dca": "UpdatePrice(uint8,uint32)", "0xf8ec4bf2": "setAllowTransactions(bool)", "0x53d97e65": "setPrizes(uint32[])", "0xd1b1a22b": "set(string,uint256[],uint256[],uint256[],bool[],uint256[])", "0x96286cc9": "isTokenOwner(address)", "0x154af6b1": "sendShares(uint256,uint8,uint256,address)", "0xbe2430fe": "sendValues()", "0x57a373a1": "uintInArray(uint256,uint256,int256,uint256[],uint256)", "0x8fd28bcf": "testFailAuthorityAuth()", "0x89ef40e7": "numberOfHealthyGenerations()", "0x23e9c216": "setBounty(address,string,uint256)", "0x71dd99fe": "BigRisk()", "0x1e9fcc77": "activateAllowance(address,address)", "0x561e91a1": "makeBet()", "0x32d2fb9f": "getRefRemainingTime(uint256)", "0x992c870d": "transferName(bytes,address)", "0x6b3fdc5a": "oraclize_setNetwork(uint8)", "0x2ea459b8": "claimThrone(bytes)", "0x33a99e04": "selectWinner()", "0x3b49a77b": "hasConfirmed(bytes,address)", "0xa352f1a8": "calcSHA3(bytes)", "0x4bb4b260": "cashAllOut()", "0xb89a73cb": "isShareholder(address)", "0xba5a2d33": "exitPool(address)", "0xddd41ef6": "transferDirector(address)", "0xa06cab79": "Registrar(address,bytes32)", "0x871113c3": "oraclize_query(string,string[1],uint256)", "0x1f0f711f": "discontinue()", "0x632f0ba6": "descriptionHashes(bytes)", "0x980e8c81": "FutureBlockCall(address,uint256,uint8,address,bytes,uint256,uint256,uint256)", "0x4ae8c55f": "getWwLength()", "0x82fc49b8": "setCosignerAddress(address)", "0xc4bd8ebc": "num_monsters()", "0x0381cb3b": "setRowcol(uint256,uint256[2])", "0x124eaee6": "Identity()", "0x3f4be889": "callContractAddress()", "0xef3a6031": "testBaseToken()", "0x954ab4b2": "say()", "0x1b855044": "getHash(uint256,uint256)", "0xd9d73887": "Diana()", "0x5103a5a3": "certify(address,bytes32)", "0x51560da9": "topDogInfo()", "0xf3ee6305": "removeCertificationDocument(address,bytes32)", "0x049ae734": "scheduleCall(address,bytes4,uint256,uint256,uint8)", "0xd8a8e03a": "move(uint256,address)", "0xc3c5a547": "isRegistered(address)", "0x06005754": "nameRegAddress()", "0xbe592488": "validateName(bytes)", "0x0eecae21": "draw()", "0xac3e7d24": "addChainyData(string)", "0xfd83f3e3": "QueueUserMayBeDeliveryDroneCotnrol()", "0x7772a380": "isInGeneration(address,uint256)", "0xeb1ff845": "changeId(uint256,uint256,uint256)", "0x9cc9299e": "killSwap()", "0x1e2ca0f3": "updateLeftLottery(address)", "0x998446a8": "acceptRequest(uint256,bytes)", "0x8e1ffb19": "testThrowsRetractLatestRevisionEnforceRevisions()", "0x9935935f": "setResolveHandler(bytes,address)", "0xcd4b6914": "getRandom(uint256)", "0xc08dd1dc": "IOU(string,string,uint8)", "0xbe4054b9": "commitReading(address,uint256,uint256,string)", "0xbc21ce9d": "Aggregation()", "0x6e173a7f": "storeBlockHeader(bytes,bytes)", "0x114d69b2": "setCRLaddr(address)", "0x3fa4f245": "value()", "0x69573648": "remove(bytes,bytes)", "0x7fee4ecb": "GAS_PER_DEPTH()", "0x591c515f": "append(string,string)", "0x727b1cd6": "next_draw(bytes32,uint256,uint256,uint256,uint256,uint256)", "0xc60ce271": "findNextMinute(uint256,bytes)", "0xd337616e": "resetLottery()", "0xdacaeb07": "pledge(bool,uint256)", "0xb29c2493": "token(uint256,string,uint8,string)", "0x61047ff4": "fibonacci(uint256)", "0x8f367001": "numTokensAbleToPurchase()", "0x12cc08f2": "getPackageReleaseHashes(string,uint256,uint256)", "0x67a59d91": "scheduleCall(address,bytes,bytes,uint256,uint256,uint8)", "0xe6c3b4ab": "testBalanceAuth()", "0xd526b9bd": "_allow()", "0x29de91db": "setMsg(address,uint256)", "0xd1cf113e": "multiAccessSetRecipient(address)", "0xc2412676": "Token()", "0x391f2e96": "InvestCancel()", "0xc0ae6a3a": "ultimateOutcomes(bytes)", "0x202d6eaf": "addInvestorsValue(uint256)", "0x30b9af98": "withdrawFunding()", "0xe80bd3e5": "addCertificationDocumentToSelf(bytes32)", "0xf4e36afd": "findThroneByNameHash(uint256)", "0x30677b83": "multiplierFactor()", "0x590528a9": "sellShares(uint256,uint8,uint256,uint256)", "0x01cceb38": "setExpiry(uint256)", "0x779beca0": "getNumOfSalesWithSameId(bytes)", "0xac940823": "betOnLowHigh(bool)", "0x06961560": "DAO(address,uint256,uint256,uint256,address)", "0xd42bf301": "doTriggerTryAuth()", "0xfa566ddd": "doAllowance(address,address)", "0x6677febe": "get_all_accepted()", "0xaa67c919": "depositFor(address)", "0xf28386bf": "Nexium()", "0x77e4fb04": "testFailNotEnoughValue()", "0x12b58349": "getTotalBalance()", "0xc0d2834b": "DataSource()", "0x3e82055a": "addSignature(uint256,bytes16,bytes)", "0xcff2fa42": "_returnFee(address,uint256)", "0xa056469a": "extractFeeLength()", "0xc98031be": "hintURL(int256,bytes32,string)", "0x6ebbe863": "updatePublishContract(address)", "0x08216c0f": "createHumanStandardToken(uint256,string,uint8,string)", "0xc36af460": "getLatest()", "0xdb5b4183": "oracleOutcomes(bytes,address)", "0x0b5ab3d5": "destroyDeed()", "0xe1c7392a": "init()", "0x4ca1fad8": "addRequest(uint256)", "0x305b73d9": "configure(address,address,uint256,uint8,bytes32,bytes32)", "0x9077dcfd": "submitCoding(string,uint256)", "0x38fff2d0": "getPoolId()", "0x07bc6fad": "withdraw(address,uint256,bytes32,uint256)", "0xfbf58b3e": "transfer(string,address)", "0x1d8b70da": "order_received(string)", "0x0b3ed536": "claimDonations(uint256)", "0x6f374a12": "setBool()", "0x0ca35682": "recover(uint256)", "0x3ae7cdfa": "fipsLegacyRegister(bytes20[],address)", "0xe6c1beb4": "prepend(address)", "0x776d62f6": "costs()", "0xe4690a0b": "popRequest()", "0x74eb9b68": "isAccountLocked(address)", "0x7d32e7bd": "transfer(address,bytes32)", "0xdf2f0a4a": "getDecisionBlockNumber(uint256,uint256)", "0xc494f71a": "LedgerFund(uint32,uint32,uint64,uint64)", "0x446d5aa4": "getAttributes(address)", "0x4cdc6a73": "Marriage()", "0x677cee54": "SafeConditionalHFTransfer()", "0x7b48ba20": "testThrowDisownNotOwner()", "0x1288c42a": "Prism()", "0xe8b13c44": "getChainyTimestamp(string)", "0xe4c2db06": "getPreviousFile(bytes)", "0xf0586f0d": "doThrow(bool)", "0xc1b06513": "registerEvent(bytes32[])", "0x521eb273": "wallet()", "0x32254992": "getPrevHash(int256)", "0x1fd96b69": "ManagedAccount(address,bool)", "0xabf74a93": "pitFee()", "0xa480ca79": "collectFees(address)", "0xa0bde7e8": "getShareDistributionWithTimestamp(bytes32)", "0xff27c476": "shiftBitsRight(bytes,uint256)", "0x172d8a30": "setDirectorLock(uint256,uint256)", "0xf262de8c": "add_staircase(uint16)", "0x990f3f53": "computeResponseSecondHalf(uint256,uint16)", "0x26745909": "PRNG_Challenge()", "0xcacc24eb": "transferFromViaProxy(address,address,address,uint256)", "0x94f3f81d": "removeAuthorization(address)", "0x3f0ec70b": "RequestFactory(address)", "0xa2a8336f": "claimEtherSigner(uint256)", "0xaa5d4719": "getTransferable(bytes20)", "0x23cd7cd5": "Model()", "0x3fb0b2c9": "CancelRoundAndRefundAll()", "0xd5fa2b00": "setAddr(bytes32,address)", "0xa0f61310": "FakeRelay(bytes)", "0x4ea66c38": "buyinInternal(address,uint256)", "0xbe040fb0": "redeem()", "0xb845c9a2": "WEI()", "0x26a7985a": "getMaximumCallGas()", "0x06661abd": "count()", "0xc89f8f08": "testGetController()", "0x81baf820": "BlockScheduler(address)", "0x9801cb8e": "ProofOfExistence()", "0xeb7492d1": "testTotalSupply()", "0x3dfb4843": "renewDeed(bytes32)", "0xc3fa5f93": "BlockScheduler(address,address)", "0x7958533a": "meta(uint256,bytes32)", "0xa1a66e56": "deductFunds(uint256)", "0xaf92a693": "addRegistrar(address)", "0xb2aac51f": "lookupUser(string)", "0xd70cf105": "moveBalance(address,address,uint256)", "0x2afb21bc": "InvestWithdraw()", "0x6d09e2ec": "commitCurrency(address,uint256,uint256)", "0x7b1a4909": "transferETH(address,uint256)", "0x96c824a8": "createAccountFundContract()", "0xe0a70811": "restart(bytes20,bytes)", "0x22057bc7": "getAllRevisionBlockNumbers(bytes20)", "0x6af2da2f": "testKeyedHash()", "0x7f6d8955": "RegisterOne(uint32,address,address)", "0x65f27bea": "testSubBalanceFailsBelowZero()", "0xa2f16d80": "dexWithdrawCollectedFees()", "0xc179520c": "ManageAccount()", "0x2672b3e2": "SplitterEtcToEth()", "0xe839e65e": "query2(string,string,string)", "0x39f64b52": "calcTokenPrice()", "0x4ef5710a": "WatchNumberOfPlayerInCurrentRound()", "0x3017fe24": "callAPIVersion()", "0x2977b1b1": "testAllowanceStartsAtZero()", "0x531c1b33": "getOperatingBudget()", "0xb7f2f33c": "transferRightIfApproved(address,bytes)", "0x00873367": "comparisonchr(string)", "0x2a0d79ef": "totalSupply(bytes)", "0xa715ff59": "EtherandomProxy()", "0xd6ca8ccb": "disown(bytes20)", "0x6ad2a0b3": "buildContract(address)", "0x45596e2e": "setFeeRate(uint256)", "0x0e97cfdf": "placeOrder(uint256,uint256,uint256)", "0x9549355e": "oracalizeReading(uint256)", "0x8d7af473": "numberOfProposals()", "0x728af7ec": "getInterest(uint256,uint256)", "0x11b9fee8": "ForkChecker(uint256,bytes32)", "0xd850288b": "etherlist_top()", "0xf4dc2d21": "Deed(uint256)", "0xf8b11853": "getGenerationStartAt(uint256)", "0x7c7a52bf": "newChallenge(uint256,address)", "0xd2d4bd72": "getCrossRate(bytes,bytes)", "0xe9b93569": "OwnerGetFee()", "0xfb72d24e": "shift_right(uint64,uint256)", "0x112e39a8": "scheduleCall(uint256)", "0x6c494843": "multiAccessChangeOwnerD(address,address,address)", "0x313ce567": "decimals()", "0x9bac8602": "testFailAddBalanceAboveOverflow()", "0xa70a9ad7": "switchDeity(address)", "0x6a61e5fc": "setTokenPrice(uint256)", "0x990c8f79": "returnValue()", "0xa4136862": "setGreeting(string)", "0x0af4626d": "testRetract()", "0x5e11544b": "newPeriod()", "0xdc206e5f": "oraclize_query(uint256,string,string[])", "0xcaa648b4": "getTotalValue()", "0x20bfec70": "WatchFees()", "0x62a0b56b": "testUnset()", "0x42f6e389": "isModule(address)", "0x769796fe": "resetAction(uint256)", "0x402e6230": "getTotalGambles()", "0xe8a1c08f": "nibbleToChar(uint256)", "0x1aa3a008": "register()", "0x96d02099": "rsplit()", "0x83324e8c": "numGroups()", "0x72c7c85a": "minority()", "0xb8d94b95": "buildDSNullMap()", "0xe039e4a1": "getOwner(uint8,uint8)", "0x625cc465": "baseDonation()", "0x77372213": "setName(bytes32,string)", "0xa7dfc874": "unregister(bytes,address,uint256,bytes)", "0x37f4c00e": "anchorGasPrice()", "0xb2bfd948": "checkNumbers(uint8[3])", "0x512f1e64": "orderBookLength()", "0xafed762b": "toSlice(string)", "0xbb6a1427": "testThrowRestartEnforceRevisions()", "0x734d8287": "unclaimedFees()", "0xf295206f": "_unsafeSend(address,uint256)", "0x69d01268": "concatUInt(uint256)", "0x0494630f": "oraclize_query(uint256,string,string[4],uint256)", "0x13fc6ac2": "getEventData(bytes32)", "0xbff974e8": "getContentReplies(uint256)", "0x18921de4": "addSignature(string,uint256[],uint256[],uint256[],bool[],uint256[])", "0xa87e7552": "isValid(bytes,bytes)", "0xb8d3bfe3": "MeatGrindersAssociation(address,address,uint256,uint256,uint256,address)", "0x61461954": "execute()", "0xecb0256b": "relayTx(bytes,int256,int256[],int256,int256,bytes,int256,int256[],int256,int256)", "0x7cdbae63": "addRegistryIntoTagsIndex(address)", "0x1f4e996b": "challenge(bool)", "0x0eb0afa6": "createDebt(address,address,uint256)", "0x5f6a1301": "clearPending()", "0x305a762a": "getTicketsCountByBuyer(uint256,address)", "0x724ae9d0": "getMinInvestment()", "0x1e39499d": "scheduleCall(address,bytes,uint256)", "0x4f197ee7": "transferPackageOwner(string,address)", "0x7e3faec1": "GoldTxFeePool(address,address,bytes)", "0x8d68cf59": "sendFunds()", "0x83eed3d5": "queryN(uint256,string,bytes)", "0x15c91115": "pbkdf2(bytes,bytes,uint256)", "0xeb121e2f": "update(uint256,uint256[101][])", "0x5e44daf3": "vote(uint256,int256)", "0xac562666": "freezeCoin()", "0xb0166b04": "testTransferringMkr()", "0x631de4d6": "replace(address,address)", "0x4bd70ea3": "testFailGetUnset()", "0xf738e5ca": "ownerTakeProfit()", "0xc6236a5c": "scheduleCall(bytes,uint256,uint256,uint8,uint256)", "0x119aa5c8": "checkForward(bytes)", "0x541aea0f": "put(uint256,uint256)", "0x6386c1c7": "getUserInfo(address)", "0x4e209678": "testFailBreach()", "0xe9fe799e": "registrantRemove(address)", "0x2aee19c7": "testCreateWithNonce()", "0xa0ec4e09": "getUltimateOutcomes(bytes32[])", "0x4d9e4e22": "Etheria()", "0xa6b513ee": "finalPrice()", "0x82f0d875": "makeHash()", "0x78ae88d1": "newDeal(uint256,uint256,uint256,uint256,uint256)", "0x177766e6": "getOptionChain(uint256)", "0xf1173928": "RemovedFromGeneration(address,uint256)", "0xea2ea847": "testChallengeFinalize()", "0xbd35d570": "GAS_TO_COMPLETE_EXECUTION()", "0x364ea9e7": "set(uint256,uint256,bool[],uint256[])", "0x17ff0caa": "WeatherBet(uint256,address,address,address)", "0x4e23a144": "fundUser(address,uint256)", "0x144267e0": "refundSecurity(address,uint256,uint256)", "0x31c6c4cf": "transferFromWithReference(address,address,uint256,bytes32,string)", "0x2eb5c61f": "testThrowsUpdateLatestRevisionEnforceRevisions()", "0xde640e19": "Investment(uint256)", "0x9cb8a26a": "selfDestruct()", "0x9c43d950": "registration(uint256,uint256,uint256)", "0xe2fdcc17": "escrow()", "0xc618d15f": "ConvertNumbers(bytes5)", "0x8c98117c": "getBill(uint256,uint256)", "0x2d7788db": "rejectRequest(uint256)", "0xfaab9d39": "setRegistrar(address)", "0xa289673b": "fipsChangeOwner(bytes20,address,address)", "0x54d9d6f8": "findNextDay(uint256,bytes)", "0x008a745d": "claimDividendShare(uint256)", "0x6f4812e2": "testFailControllerInsufficientFundsTransfer()", "0x5e983d08": "setPrices()", "0x798974dd": "getNumProposals()", "0x4ca168cf": "register(bytes,uint256,address,string,uint256)", "0xa1e4d3c2": "MembershipRoster()", "0xd4065763": "returnRemainingMoney()", "0x0c4326a0": "getMajorMinorPatch(bytes32)", "0xeece1e1f": "scheduleShuffling()", "0x226685ee": "Visit()", "0x323082d7": "Vote(string)", "0x0b15650b": "randInt(uint256,uint256)", "0xc9cfac55": "refundCurrency(address,uint256,uint256)", "0xfe4a3ac9": "setExecPrice(uint256)", "0x6a0e605f": "MyToken(uint256,string,uint8,string,address)", "0xb549793d": "scheduleCall(bytes4,bytes,uint256,uint256,uint8,uint256)", "0x85e68531": "revokeAccess(address)", "0x01991313": "scheduleCall(address,bytes4,uint256)", "0x0a6be0e7": "BalancedPonzi()", "0xf463edd1": "createDocument(uint256)", "0xa20c404f": "ModifySettings(uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x560bb612": "SignatureValidator(address)", "0xf7654176": "split()", "0x48f05187": "scheduleCall(address,bytes4,bytes,uint256)", "0xf2b904c3": "checkBetColumn(uint8,address,bytes32,bytes32)", "0x7bc0ff20": "setupExportFee(address,uint256)", "0xeb06e65e": "allowanceFromProxy(address,address,address)", "0xfe757fb5": "lastClaimPrice()", "0xa5d0bab1": "buyPartial(uint256,uint256)", "0xda7d0082": "isCertification(address,bytes32)", "0xe570be18": "DVIPBackend(address,address)", "0x54738157": "OwnerCloseContract()", "0xc1e5304a": "CreateNewDraw(uint256,bytes)", "0x0c26e42e": "getReleaseHashForNameHash(bytes32,uint256)", "0x0f7d6673": "Channel()", "0x54ea4000": "identify(address[])", "0x69307c80": "rotateBits(bytes,int256)", "0x78f0161a": "setGreyGreenPrice(uint8)", "0x23b872dd": "transferFrom(address,address,uint256)", "0x578bcc20": "reduceDebt(address,address,uint256)", "0x59e148fc": "getLastOfferId()", "0xb5299ca6": "giveMeat()", "0xae30d35d": "ARK_TROGLOg_1_00()", "0x2d2c44f2": "Vault()", "0xce19419b": "testThrowsSetNotUpdatableNotOwner()", "0xffcf21a9": "eventOracles(bytes,uint256)", "0xf46c50dc": "doFail()", "0x73b55eaf": "registerData(address,int256,bytes32,address)", "0x53770f9a": "isStateless()", "0x4d47feaa": "ShareholderDB(uint256)", "0x40b31937": "pledgeDecline(uint256)", "0x01cb3b20": "checkGoalReached()", "0x62e05175": "setMotionDB(address)", "0xf362d78f": "testBitNotEqualSuccess()", "0xd2531590": "CANCEL_EXTRA_GAS()", "0x9a92b7e7": "EthVenturesFinal()", "0x79b0797c": "AmIPlayer1()", "0x6241bfd1": "Token(uint256)", "0x94a1710d": "testNonOwnerCantBreach()", "0xb466b76f": "fresh()", "0x4a5dddd2": "proxyPurchase(address)", "0xc0a1a949": "x15()", "0xc3b8bfe5": "transferIfNoHF(address)", "0x4a7e049e": "getFullCompany(address,uint256)", "0x481b659d": "permitPermanentApproval(address)", "0x16ce8a69": "setBuilding(uint256,uint256)", "0x1593a8c7": "endLottery()", "0x078c3fa4": "_transferToICAPWithReference(bytes32,uint256,string)", "0xfa3f1e99": "testBlobStoreRegistered()", "0x0b9e9817": "CanaryV7FastTestnet()", "0x6663bbec": "orderMatch(uint256,uint256,int256,uint256,uint256,address,uint8,bytes,bytes,int256)", "0x273bc3c9": "numberOfThrones()", "0x3c84f868": "set(int256,address,uint256)", "0x1ac61e8c": "testBlobCreate()", "0x5ccc3eaa": "roundMoneyUpToWholeFinney(uint256)", "0x0ccec396": "getNumReleases()", "0x6ac6205c": "addDataPoint(int256,uint256,bool,string)", "0x1d124fe4": "setUtils2(address)", "0x4c471cde": "scheduleCall(address,bytes4,bytes,uint256,uint256,uint8,uint256)", "0x52a554a1": "voteBoardProposal(uint256,address,bool)", "0x745a8be2": "flip32(bytes)", "0xbac1e2e0": "testBitsAndSuccess()", "0x25fda176": "notify(address,uint256)", "0x3b8e6f2e": "balanceAt(address,uint256)", "0x60585358": "getByte()", "0xc853c03d": "newDraw(uint256,uint8[3],uint256,uint256,uint256,uint256)", "0x741273d6": "testThrowRegisterContractAgain()", "0x8f2c44a2": "UnicornMilker()", "0x59d96db5": "terminate(uint256,string)", "0x483ba09e": "setBitcoinBridge(address)", "0x74fbbc86": "rate(uint256,uint256,string)", "0x83ea0620": "packageExists(string)", "0xd917deb5": "Donate()", "0x3fc6bc94": "payDAO()", "0x6558488a": "scheduleSetBool(address,uint256,bool)", "0x83e78b31": "bet(uint8,bool,uint8)", "0xeccb15bc": "SatPosition(int256,int256)", "0x7daa10ce": "getMyInfo()", "0x3e4565d2": "testErrorUnauthorizedNameRegister2()", "0x2143da91": "GameOfThrones()", "0xb29f0835": "doIt()", "0xdcc0ccf3": "Dao(address)", "0x70d53be5": "find()", "0x9a828a71": "oracalizeReading(uint256,string)", "0x6a6d31db": "externalEnter()", "0xf8b71c64": "rewardTo(address,uint256)", "0x0399c357": "assignFreeReadings(address,uint8)", "0x81ade307": "query(string,string)", "0xdb83694c": "getSaleInfo()", "0xa6bf3df0": "oraclize_query(string,string[2],uint256)", "0x29605e77": "transferOperator(address)", "0xf29d2f28": "setTokenHolder(address)", "0xa96f8668": "releaseTokens()", "0x8a3bc2bc": "iPropose(bytes,uint256,bool)", "0xd18611d6": "reactivate()", "0x7620a65b": "Publisher()", "0xa268b332": "testBitXorFailIndexOOB()", "0x6b1781b6": "Emergency()", "0x1003e2d2": "add(uint256)", "0x1209b1f6": "ticketPrice()", "0xe5a27038": "Pluton(uint256,string,uint8,string)", "0x22bc3b8e": "getArgument(uint256)", "0x47bdb7f4": "transferDisable(bytes20)", "0x13137731": "testThrowsUpdateLatestRevisionNotUpdatable()", "0x3f3935d1": "confirmReverse(string)", "0xecb4136e": "NotAnotherPonzi()", "0x49e347ae": "getContents(uint256[],uint256)", "0x669dafe8": "toWei(uint256)", "0xc233e870": "isLatestPatchTree(bytes32,bytes32)", "0x7b789b3d": "agreement(bytes,bytes,bytes)", "0x682d3bb0": "pdfCertificateProof(bytes)", "0x42346c5e": "parseInt(string)", "0x3177029f": "approveAndCall(address,uint256)", "0x71ffcb16": "changeFeeAccount(address)", "0xc971442c": "getDBs()", "0x362e2565": "returnDeposits()", "0xe10e274a": "CrazyEarning()", "0x6d705ebb": "register(address,uint256)", "0xbe9a6555": "start()", "0x1ce624d6": "Crypted_RPS()", "0x2c4cb4be": "removeRegistryFromNameIndex(address)", "0x68742da6": "withdrawFunds(address)", "0x18f3fae1": "setOversight(address)", "0x061ea8cc": "countByOwner(address)", "0xd6d22fa4": "MetaCoin()", "0x85654c9c": "setMembershipRoster(address)", "0x8aa33776": "setMsgPrice(uint256)", "0x4dd850fb": "UfoPonzi()", "0x07e00bcb": "kissBTCCallback(uint256,uint256)", "0xa1b7ae62": "setdirectorName(string)", "0xb4d9cc3a": "profitDisperser()", "0x0f24f5c8": "doTransfer(address,uint256)", "0x8d72a473": "deductFunds(address,uint256)", "0x28f03554": "ProcessDividend()", "0x98391c94": "muteMe(bool)", "0x346cabbc": "scheduleCall(address,bytes4,uint256,bytes,uint256)", "0xa42e36c6": "scheduleTransaction(address,bytes,uint8,uint256[5],uint256)", "0x21b36a08": "setFee(uint64,uint256)", "0xd94073d4": "PT()", "0xe8580dd4": "Survey(address,uint256,string,bytes32[])", "0x1f0c1e0c": "getEventTokenAddress(bytes32,uint256)", "0xce8b7151": "isHF()", "0x9bee757b": "requestExecution(bytes,uint256)", "0x775dec49": "keccak()", "0x6673ce2b": "Results_of_the_last_round()", "0x9f87acd0": "exec(bytes32,bytes32,uint256)", "0x02394872": "getLastBlockHeight()", "0x615664ba": "Market()", "0x0d7af726": "addGame(address,string,string)", "0xf4aa1291": "withdrawFundsAdvanced(address,uint256,uint256)", "0x8ed67a44": "setPrice(uint16)", "0x84ebde52": "Under_the_Hood()", "0x5a28340a": "accessOperatingBudget(uint256)", "0x9a89ad65": "within6Confirms(int256,int256)", "0xdfce8ac3": "fipsLegacyRegister(bytes20,address,bytes)", "0x73f310df": "multiAccessRemoveOwner(address)", "0x4cbee813": "logout(string)", "0xd992bd5b": "testResultNotZero()", "0x05b34410": "creationDate()", "0xfed4614b": "funeral(bytes,int256)", "0x58cb7323": "MainnetETCSurvey()", "0xbb504317": "divest(address,uint256)", "0x82381c96": "WatchCurrentMultiplier()", "0xcce81927": "EtherDice(address,address)", "0x70961774": "getBlockCreatedOn()", "0x84a7b223": "Canary(address)", "0x9378a9e2": "setUInt(uint256)", "0xe4360fc8": "getFileListElement(bytes)", "0xe597f402": "create(bytes1,bytes32,bytes)", "0x95d5a1be": "SignatureReg()", "0x33ce7787": "transferInvestorAccount(address,address)", "0x46c52b1a": "blockHexCoordsValid(int8,int8)", "0x3092afd5": "removeMinter(address)", "0x30945443": "update(address,string,string)", "0xc37ff3d9": "sha(uint256,uint256)", "0x29a6f31b": "oraclize_query(uint256,string,string[2],uint256)", "0x227f9633": "addOption(string,address,uint256)", "0x38eaf913": "setDirectorNode(string)", "0xab67aa58": "transferFrom(address,address,uint256,bytes)", "0x0ce3151c": "personUpdateRelation(uint256,string)", "0x216ef940": "proxyUpgrade(address,address,bytes)", "0x76bc21d9": "fireEventLog2Anonym()", "0xf004073a": "performAction(uint256)", "0xdba7ef7d": "Bookie(address,address)", "0xa0469b02": "inputToDigit(uint256)", "0x1d007f5f": "changeDAO(address)", "0x9dcb5c65": "resultsWeightedByEther()", "0x14ab9038": "setTTL(bytes32,uint64)", "0xf4d94699": "EndowmentRetriever()", "0xe74b9d11": "safeToSubtract(uint256,uint256)", "0xd7504385": "validateToAddress(address)", "0x57e2880d": "scheduleTransaction(uint256,uint256)", "0xe73a914c": "setDAO(address)", "0xc47bc007": "add_funds()", "0x37881810": "setCallbackAddress(address)", "0x686f2c90": "collectAllFees()", "0x278b8c0e": "cancelOrder(address,uint256,address,uint256,uint256,uint256,uint8,bytes32,bytes32)", "0xfac34ff6": "throwFoo()", "0x6d98e9fc": "totalWei()", "0xb0bcc610": "scheduleTransaction(address)", "0x665bcc32": "ProcessGames(uint256[],bool)", "0x3fd1f232": "LookAtAllTheseTastyFees()", "0xdd727ea6": "runJackpot()", "0x0acc4382": "getMinDailyWithdrawLimit()", "0x46b207b8": "checkExpiry()", "0xde5d953a": "logSingleIndex(bytes,bytes,uint256)", "0xf504e0da": "load_level(uint16)", "0x4b63e601": "scheduleCall(address,uint256,bytes)", "0x4a71d469": "collectRev()", "0x80db79d9": "StructAndFor()", "0x090637a1": "GetPart(bytes,uint256)", "0xc003b082": "getMyPlayerID()", "0x00a7d6b3": "checkTransferFromToICAP(address,bytes32,uint256)", "0xdcf8113e": "campaignEndedSuccessfully()", "0xd1af8a5a": "LinkerExample()", "0x01fd89a4": "getFlags(bytes20)", "0xa39a45b7": "replaceOwner(address)", "0x0a3b1cd2": "setHotwallet(address)", "0x075fe877": "scheduleCall(address,bytes,uint256,uint256)", "0x3e5fd9b5": "dEthereumlotteryNet(address,address,bool,address)", "0xa6403636": "resolve(uint8,bytes32,bytes32,bytes32)", "0x0b2acb3f": "add(address,bytes)", "0x6d522b19": "multiAccessChangeRequirementD(uint256,address)", "0x4311de8f": "ownerWithdraw()", "0xa99e7e29": "register(bytes,address)", "0x1ed6f423": "changeDescription(address,string)", "0xcd50d44f": "CheckRepresentment()", "0x4c0e207a": "__outputCallback(uint256)", "0xea8a1af0": "cancel()", "0x67387d6b": "testThrowCreateWithNonceExistingNonce()", "0xdc583801": "doubleyour5()", "0xb8077e28": "getTxOrigin()", "0xbfbc793c": "computeNameFuzzyHash(string)", "0x79baa8a9": "BasicIncome_CoFund()", "0xf4dbeb9d": "getCredRanksByContents(address,uint256[])", "0x227185d6": "Send1Get2()", "0x75c4aaa6": "addUnderDog(uint256)", "0xa7abc124": "activate(bool,bool)", "0x8df554b3": "Dividend()", "0x092b25e9": "setOwner(string,address)", "0x67af26fb": "transferOtherFrom(address,address,address,uint256)", "0x4bb278f3": "finalize()", "0xd1e15045": "sendBack()", "0xa4699cad": "resetWithdrawls()", "0xb61d27f6": "execute(address,uint256,bytes)", "0x9772c982": "scheduleCall(address,bytes4,bytes,uint256,uint256)", "0x1b3a8e6f": "directionCount(int256,int256,int256,int256)", "0xd499555b": "getFirstActiveDuel()", "0xb738169c": "betOnOddEven(bool,bool)", "0x411c4e72": "ModifyFeeFraction(uint256)", "0x06f36cc9": "helpBlue()", "0x9e65c7e5": "updateLatestRevision(bytes20,bytes)", "0xb47fa7e0": "DepositLimit(uint256)", "0xf1736d86": "m_dailyLimit()", "0x62ea82db": "bids(address)", "0x4166c1fd": "getElevation(uint8,uint8)", "0x8702735c": "setCapitol(uint256,uint256)", "0x3cc7790a": "GSI()", "0x83f6d9a4": "validateNameInternal(string)", "0x8d99b2eb": "endPoll()", "0x8bbda7e3": "setContent(string,bytes)", "0x52efea6e": "clear()", "0x2581c674": "testBitsOrFailIndexOOB()", "0x05d87fe2": "issueLetterOfCredit(uint256,uint256,uint256)", "0xcbf0b0c0": "kill(address)", "0xf83d96c1": "InsuranceAgent()", "0x8dd5e298": "canEnterPool(address)", "0x2d580ef6": "add(address,bytes32)", "0xeeda149c": "Register(address)", "0xcc25decd": "SampleOffer(address,bytes,uint256,uint256,uint256,uint256,uint256)", "0x428d64bd": "getShares(address,bytes32[])", "0x3c9a4baa": "requestOutput(bytes)", "0x8cae1374": "editBlock(uint8,uint8,uint256,int8[5])", "0x419db07b": "generousFee()", "0x202e3924": "getOperation(uint256)", "0x5ee345e4": "computeEndowment(uint256,uint256,uint256,uint256,uint256,uint256)", "0x7df23b6a": "ReleaseOracle(address[])", "0x9b2ea4bd": "setAddress(string,address)", "0x65093661": "newCommunity(address)", "0x33637d5a": "getPendingBlock(uint256)", "0x7910085d": "fipsIsRegistered(bytes20)", "0x730720b8": "testControllerValidTransfers()", "0xb0c80972": "setBalance(uint256,bool)", "0xdcf537b1": "multiply7(int256)", "0xdf5cc291": "get4(bytes,uint256)", "0x9ae4e388": "ChangeClientTokenAccount(address,bool)", "0x3121369d": "validateRequiredStackDepth(uint256)", "0x1747dfd4": "ContractPlay()", "0x598647f8": "bid(uint256,uint256)", "0xc368109c": "monster_hp(uint256)", "0x7fa22001": "assertEq0(bytes,bytes,bytes)", "0x8e280dce": "findNextYear(uint256,bytes)", "0x39d1f908": "actualBalance()", "0x8143f8a8": "totalGas(bytes)", "0xfe55932a": "setName(uint256,string)", "0x0fbf7151": "startsWith()", "0x4f20f35a": "payExpenses(address,uint256)", "0x705eeb90": "MultipleConstructorTest(bool)", "0x2df8e00d": "becomeMortal(uint256)", "0x645dce72": "updateRelease(uint32,uint32,uint32,bytes20,bool)", "0x1f6e5117": "getCallbackAddress()", "0xf51cbc72": "Level()", "0x64edfbf0": "purchase()", "0x35930e13": "setMinimalRewardedBalance(uint256)", "0x015e4f3a": "getConfigUint(int256,bytes)", "0x2c329e99": "Last_block_number_and_bloctime_used()", "0x6f3a7561": "SimpleAuction(address)", "0x6de00927": "GetUserRank(uint8,address)", "0xbe600276": "move(uint16)", "0x27d6c032": "unregister(bytes)", "0x4188d79c": "releaseExists(string,uint32,uint32,uint32,string,string)", "0x7ba38916": "changeAdminFromBoard(address)", "0x3369dace": "flipTheCoinAndWin()", "0xfa8dc33a": "checkRecordExists(bytes)", "0xebaf7f2f": "returnReward(uint256)", "0xc88961da": "createKingdom(string,address,address,address)", "0x21970c0c": "pay_royalty()", "0xb4a5ef58": "updateDefaultTimeoutPeriod(uint256)", "0x57bcccb6": "revokePermanentApproval(address)", "0xd1d1c8ae": "ConvertNumbers(bytes)", "0xc1c0e9c4": "exec()", "0xcc131be1": "CreateNewDraw(uint256)", "0x75f96ead": "Guess(uint256)", "0x8a5fb3ca": "currentFeePercentage()", "0x550bcd8d": "testThrowUpdateLatestRevisionEnforceRevisions()", "0xa6780857": "fireEventLog0Anonym()", "0x2d0104a5": "updateFirstDuel1(uint256)", "0xcbf1304d": "balances(address,uint256)", "0xdda9939c": "Store(address[])", "0xf41bfa9e": "mint(int256,uint256,string)", "0x044215c6": "token(uint256)", "0x1f903037": "getBytes32()", "0xa6f9dae1": "changeOwner(address)", "0xf9391d24": "AllPayAuction()", "0xabebb7f3": "MarketsContract()", "0x9e1e6528": "uncertify(address)", "0x81788e2b": "addAllowedAddress(address)", "0x4f44728d": "ownerChangeOwner(address)", "0x3da0ac79": "compare()", "0x96e438a1": "reclaimDeposit(uint256)", "0x5fe22c8b": "testFailTransferWithoutApproval()", "0x6835f32c": "build(bytes)", "0x5cac8b27": "amazing()", "0xad605729": "getParticipantCount()", "0xb6294bde": "AdminGetFee()", "0xec81e22e": "returnmoneycreator(uint8,uint256)", "0xc535165f": "revealAndPayout(bytes,bytes)", "0x6e0bd282": "destroy(bytes32)", "0xdda44b10": "buyRecipient(address,uint8,bytes32,bytes32)", "0xd4859dbc": "UniversalFunctionSecure(uint8,bytes32,bytes32,bytes32,bytes32,bytes32)", "0xd2602930": "RockPaperScissors()", "0xa08b3367": "EC()", "0x92d66313": "getYear(uint256)", "0xe49dcee9": "fixTokens()", "0x36555b85": "add(string,uint256)", "0x25010816": "get_length(uint256,uint256)", "0x610d5de8": "validateEndowment(uint256,uint256,uint256,uint256,uint256)", "0x79ce9fac": "transfer(bytes32,address)", "0x3ced516c": "descriptionHashes(bytes32)", "0xcf69df28": "getDataRequestLength()", "0x706dfe54": "getIssueState(uint256,bytes32)", "0x5af77fff": "Contract()", "0x66e5cb50": "stopTransfer(uint256)", "0x5f72f450": "check(uint256)", "0xf3b50c04": "rescind()", "0x57aee888": "_eraseNodeHierarchy(uint256,bytes32[],bytes32)", "0xaacc5a17": "getRandom()", "0x40275f85": "getPersonalDepositAddress(address)", "0x75700437": "query1_withGasLimit(uint256,string,string,uint256)", "0x6eb7b4c2": "underdogInfo(uint256)", "0x0f3eb785": "add(string,uint256,uint256,uint256)", "0xdc19266f": "Total_of_Players()", "0x9743dfc1": "jesterAutomaticCollectFee()", "0x6618b008": "cancelSellOrder(address)", "0x65538c73": "fireEventLog0()", "0xa4502cb8": "setExportFee(address,uint256)", "0x97bb2a63": "newvow(uint256,address)", "0xb400d149": "betOnNumber(uint8)", "0x030d406b": "entryPayout(uint256)", "0x1d71a1cd": "newIncome(string)", "0x85dd2148": "getSaleDate(bytes16)", "0x29917954": "exitPool()", "0xa25057de": "_transferToICAP(bytes32,uint256)", "0x24fc65ed": "getId(uint256,uint256)", "0x938199a5": "getDateOfLastPayment()", "0x04bb754c": "TradeFinancing()", "0xe37aa618": "distributeValue()", "0x547916ea": "finishRound()", "0xed01bf29": "budget()", "0x95ee1221": "isCancelled()", "0xfe777bcd": "etherForSale()", "0xffe302d1": "setPlz(string)", "0x891de9ed": "fromTLA(string)", "0x84734476": "copyBytes(bytes,uint256,uint256,bytes,uint256)", "0xfb114f57": "oraclize_query(uint256,string,string[3],uint256)", "0xceebe28d": "repoInterfaceVersion()", "0xb0ad38c4": "buildCity(string,uint256[2],uint256[2])", "0xefa7e56b": "GameEnds()", "0xcc3471af": "maxClaimBlock()", "0xa7c5052e": "buildDSTokenRegistry()", "0x6831c169": "totalPayedOut()", "0x98f3b81a": "getShares(address,bytes32[],int256[])", "0x2d077ad0": "Latch()", "0x0ac28725": "requestTradeDeal(uint256,uint256,string)", "0xb311ee0c": "refundClaimDeposit()", "0xadd82871": "strEqual(string,string)", "0x7879e19e": "CollectAllFees()", "0x5bd74490": "regProxy(address,address)", "0xd2b0d554": "getDisclaimer()", "0x0b74edc6": "testFinalHash()", "0x6cf761d4": "getMinConfirmationsByAddr(address)", "0x4cedf74e": "get_party1()", "0x4adcbd19": "isThisHardforkedVersion()", "0xefdecd9b": "check_withdrawdao()", "0x996a8046": "__callback(bytes32,string,bool)", "0x7c9cd7df": "changeDeveloper_only_Dev(address)", "0x3f77b560": "newDocument(bytes)", "0x06b5f02d": "calcWinnings(uint256,uint256)", "0x0a2282ae": "JackPot()", "0x378a2178": "tallyVotes()", "0xd8915fc5": "DCAssetBackend(bytes32,bytes32)", "0x0f590c36": "emergencyFixGameResult(uint64,uint256)", "0xea4af029": "ConferenceCertification()", "0x769dc523": "GetCategoryNumber(bytes4)", "0xd5df7559": "removeDocument(uint256)", "0x749aa2d9": "endRound()", "0xd8e5ae6a": "Etheramid()", "0xc0576b73": "monsters(uint256)", "0x32fefb4c": "add_account(address,address)", "0x7d619d9b": "holdCoin(address,address)", "0x5b067cce": "testCreateCostMain()", "0x384b1393": "follow(uint256)", "0x4162169f": "dao()", "0x5d8227e6": "FactoryBase(string,string,string)", "0x6bf52ffa": "Vote()", "0xeb5904c0": "setProfitDistributionContract(address)", "0x366a68dc": "setBlockLock(uint256)", "0x80d9eaa6": "refCount()", "0x89b8b492": "read(uint64)", "0x46b5e202": "set_num_levels(uint256,uint256)", "0xd96de4ce": "AdminDrawError()", "0x47b47102": "bakeCookie(string)", "0x1d7b5baf": "setUint(int256,bytes32,string,uint256)", "0x0699d07d": "updateMaxVal()", "0xfa544161": "getOwner(address)", "0x638560cf": "registerBool(address,bool)", "0x7c25f260": "Government()", "0x24a852c6": "unset(bytes)", "0xa32f0f41": "testFailControllerUnapprovedTransferFrom()", "0x0968f264": "withdraw(bytes)", "0x5f52e9fd": "WithdrawCashForHardwareReturn(uint256)", "0xc0f68859": "getMinimumGracePeriod()", "0x1bf6c21b": "USD()", "0x0fe234ed": "testSetController()", "0x05a17fc6": "getAccountFeed(address,uint256,uint256,uint256)", "0x673448dd": "isApproved(address)", "0x59dac714": "hashTo256(bytes)", "0x5a09f2f4": "setHouseFee(uint256)", "0x013cf08b": "proposals(uint256)", "0xeebf9808": "PiggyBank()", "0xadd43c59": "EtherTopDog()", "0xf909d60d": "getMinimumGasLimit()", "0xeb045789": "ChannelSeries(address)", "0x66d38203": "setup(address)", "0xe8641652": "strCompare(string,string)", "0x1959a002": "userInfo(address)", "0x737c8ea1": "_getRevisionBlockNumber(bytes32,uint256)", "0x127714c7": "getBudget()", "0x97daa043": "register(bytes,address,address,uint256,bytes)", "0xb5784f6f": "testMultiplePackages()", "0x0ce46c43": "scheduleCall(address,bytes4,bytes,uint16,uint8,uint256[5])", "0xe5782fd5": "setFeeStructure(uint256,uint256,uint256)", "0xa9f6def0": "HonestDice()", "0xeb7cdb56": "rankDown(uint256,uint256)", "0xb17b94c1": "testSystem()", "0xdd36e18b": "ContractStatus()", "0xee0dc478": "testSetEnforceRevisions()", "0x918359c6": "needsBirth()", "0xa5b1e13d": "settle(address,address,uint256,uint256)", "0x6b76484e": "swap(address,address)", "0x68402460": "scheduleCall(address,bytes4,uint256,uint256,uint8,uint256)", "0x733480b7": "transferToICAP(bytes32,uint256)", "0x567dbf18": "__forward(address,uint256,uint256,bytes)", "0x73e1743a": "buildDSBasicAuthority()", "0x482961e1": "updateReading(uint256,uint256)", "0x4e6ba0a9": "testCreateCostMultisig()", "0x8d7108e5": "isValidLocation(uint8,uint8,int8[5],int8[24])", "0x10142785": "assign(bytes,uint256,bytes1)", "0xfe97ee88": "hasPhone(address)", "0xe2861c8d": "cashOutProfit()", "0x0fa9ced4": "emergencyFuneral()", "0x8389f353": "setNumCities(uint256)", "0xdba1ac3d": "getEnforceRevisions(bytes20)", "0x1b4fa6ab": "getDefaultStackCheck()", "0x79be02af": "Read(address)", "0x70844f7a": "sendBadge(address,uint256)", "0x7bfaad96": "addNode(bytes,address)", "0x4d782cbc": "executeSellOrder()", "0xbe71248a": "payWinner()", "0x41304fac": "log(string)", "0x4f059a43": "getClaimAmountForBlock()", "0x6d2cb794": "airaTransfer(address,address,uint256)", "0x5a5383ac": "canExitPool()", "0xcabd27de": "Motion(address)", "0x433d4aab": "resolve(uint8,uint8)", "0x7d89ae63": "__findRef(string)", "0x4e1053cc": "RobinHoodPonzi()", "0x0220a5b4": "terminate(string)", "0x419ffa03": "fipsRegister(address)", "0x77a7e6be": "getRefTotal(uint256)", "0xed64bea4": "JamCoin()", "0x3cf885c4": "isBitSet(uint256,uint8)", "0xf2da67db": "setMany(uint256,int256,uint256,bytes20,address,bytes)", "0xe26c8434": "AdminStartDraw(string,bytes)", "0x13df7091": "mintAll(int256)", "0x8a46bf6d": "testFallback()", "0x29bed3bf": "EthereumRoulette()", "0xf869b11a": "declareVictor(uint256,uint256)", "0x45c41478": "getMarkets(bytes,address)", "0x90cf581c": "voteYes()", "0x9ec35352": "returnRandom()", "0x5025b9ae": "expire(uint256,uint256,uint8,bytes,bytes,bytes)", "0x338a1379": "_setPackedBlockNumber(bytes20,uint256)", "0xdb641ab4": "Game_balance_in_Ethers()", "0xbc0e7adb": "testThrowsDisownNotOwner()", "0x302d350e": "firstChainedCallback(uint256)", "0x5af73f3f": "getMinimalBalance(uint256,address)", "0x4c488dac": "getChannelValidUntil(bytes)", "0xa1616429": "testBitOrSuccess()", "0xaa64c43b": "transferPool(address,address,uint256)", "0x78e97925": "startTime()", "0xa0355f4e": "decline(uint256)", "0x02556de3": "updateMajorTree(bytes32)", "0x01984892": "name(address)", "0xfad4b99a": "updateChannelMinimum(address,uint256)", "0x4f39ca59": "drop(bytes32)", "0x61591a7c": "personUpdateDOB(uint256,int256)", "0x17b3a34b": "_addIdentities(uint256,bytes32[])", "0x91d8b14e": "BuyTickets()", "0x1aadcc34": "convertGreyGreen(uint8,uint8)", "0x9a863892": "NewProposal(uint256)", "0xc5487661": "proxyTransferToICAPWithReference(bytes32,uint256,string)", "0x85f8c16d": "claimHours(int256)", "0xa71f94c8": "scheduleSetUInt(address,uint256,uint256)", "0x7ef09476": "transfer(uint64,address)", "0x94bcdb4c": "Example2()", "0x37930615": "extend(bytes16[],uint64)", "0xfb09b1ac": "testBalanceOfReflectsTransfer()", "0x19a278b9": "getBAddress()", "0xc0a239e3": "valuePerShare()", "0xa039e3c7": "testSetNotTransferable()", "0x22593300": "Small(address)", "0xe21608be": "ReserveToken()", "0xc2985578": "foo()", "0xb463bcde": "testThrowsSetNotTransferableNotOwner()", "0x88d695b2": "batchTransfer(address[],uint256[])", "0x37b0574a": "isClassic()", "0x1da6822c": "testThrowsTransferEnableNotTransferable()", "0xdcfa9cc0": "testProxyCall()", "0x478aa69e": "unauthorizeUser(address)", "0x102accc1": "fireEventLog2()", "0xed62cf1f": "setCanCall(address,address,bytes,bool)", "0x15f73331": "invalidateName(string)", "0x73e30e49": "majorEventFunc(uint256,bytes,bytes)", "0x00c721ab": "setHand(uint256)", "0xf9e27106": "investmentEntryCost()", "0x4c7f74df": "EtherDelta(address,address,address,uint256,uint256,uint256)", "0xb74e452b": "today()", "0xd3118a5a": "addDoc(string,string)", "0xc204f9f1": "_transferFromToICAP(address,bytes32,uint256)", "0xf50d3914": "resetFoundationtList()", "0xe67cdfb7": "moveOldUser(uint256)", "0x98eaca94": "inKissBTC(uint256)", "0xc633084f": "sendGreeting(address,string)", "0xde10f04b": "eraseNode(bytes32[])", "0xd50495f4": "addTransaction(bytes)", "0x96cff3df": "getMinimumCallCost(uint256,uint256)", "0xce373b95": "heroOfThePit()", "0x39e525f9": "resolveCallback(uint256)", "0x942b90d3": "getRewardTable()", "0xedca914c": "buyTicket()", "0x5fcb568c": "release(string,uint32,uint32,uint32,string,string,string)", "0x6c9c2faf": "getSupply()", "0xf1448e10": "requestExecution(bytes)", "0x0c08bf88": "terminate()", "0x08aba5aa": "setAccountBalance(uint256)", "0x2c46d8d5": "EndRound(uint256)", "0x3d5db1c2": "incrUserOnholdBal(address,uint256,bool)", "0xf2f254c7": "getLatestMinorTree(bytes32,uint32)", "0x373a1bc3": "scheduleCall(address,bytes4)", "0x3a96fdd7": "compare(string,string)", "0x738ddabe": "getContentIndexedAccountCred(uint256,address,address)", "0x5acce36b": "getEndowmentBalance()", "0x1ca60aeb": "setMeltingContract(address)", "0x52375093": "m_lastDay()", "0x565a2e2c": "getBeneficiary()", "0x9d5c6061": "getMsgGas()", "0x41d31feb": "get_read_only_keys()", "0x796b89b9": "getBlockTimestamp()", "0x4a41e045": "getUint8(int8)", "0x38e48f06": "save(string)", "0x1cda37f2": "eraseRecords(bytes32)", "0xae978f08": "getLatestTweet()", "0x20909fa0": "communityCurrency()", "0xafbec8df": "TheGrid()", "0x1c14179a": "GavCoin()", "0x0b6142fc": "breach()", "0x3ab1e703": "roundMoneyDown3SF(uint256)", "0x414ceac0": "investorAddFee(uint256)", "0x82a62137": "activateAccount(address)", "0x4ca7fbd0": "updateTokenPriceWeekTwo()", "0x2551858e": "getFlags(bytes32)", "0x4ad07b0e": "oracleOutcomes(bytes32,address)", "0x60b431a4": "testGetSig()", "0xa5f8cdbb": "buyTicket(address)", "0x64aabe92": "tryExec(address,bytes,uint256)", "0xa6c01cfd": "isInGeneration(uint256)", "0x149c5066": "ChanceOfWinning(uint256)", "0xc068eae0": "player_collect_winnings(uint256)", "0x8129fc1c": "initialize()", "0xcf832ce2": "ownerRefundPlayer(bytes32,address,uint256,uint256)", "0x3517a740": "getNodeParent(bytes)", "0xec6afc22": "oraclize_query(uint256,string,string[3])", "0x50944a8f": "setMembership(address)", "0x85b1423e": "returnAll()", "0xd95a2d42": "lendGovernmentMoney(address)", "0x347632e8": "getShareholderAdressByID(uint256)", "0xbb39a960": "trade(address,uint256,address,uint256)", "0x8abadb6b": "setAccountLevel(address,uint256)", "0xa502aae8": "getNextGenerationId()", "0xb5bfdd73": "addDSource(string,bytes1,uint256)", "0x28d3ad3f": "getPot(uint256)", "0x08933d11": "getJoinBlock(address)", "0x8383bfc8": "EscrowFoundry()", "0x2ca15122": "sign()", "0xf340fa01": "deposit(address)", "0x9ed93318": "create(address)", "0xa1c0539d": "scheduleCall(address,bytes4,bytes)", "0xced92670": "changeMultiplier(uint256)", "0xb2c652f3": "getMarkets(uint256[128])", "0x69b144eb": "testThrowsCreateNewRevisionNotOwner()", "0x16c72721": "forked()", "0x712ca0f8": "getOrder(string)", "0x0cf45ba5": "updateFirstDuel2(uint256)", "0x4173b181": "setWeiPrice(uint256)", "0x689b3e2d": "Moonraker(address,address)", "0x8691162a": "TlcCoin()", "0x432ced04": "reserve(bytes32)", "0x38178fbe": "addString(string,string)", "0x8f1327c0": "getRound(uint256)", "0xa9eed530": "reduceOrderQty(uint256,uint256)", "0x408938d0": "testUpdatePackageDb()", "0x56105a08": "DgxSwap()", "0xc43d0575": "scheduleCall(bytes4,uint256)", "0xdba21657": "askForEther(uint256)", "0xca3b5c91": "hasRelation(bytes,bytes,address)", "0xc71cbcf3": "recoverAccount(address,address)", "0xb010d94a": "canExitPool(address)", "0x0a16697a": "targetBlock()", "0xff1f7046": "requiresAuction(string)", "0x0b811cb6": "executeProposal(uint256,bytes32)", "0xbb8be064": "HardwareToken()", "0xe2b05077": "getSaleDate(bytes,uint256)", "0x1e9a6950": "redeem(address,uint256)", "0xd21b84ac": "createNewDAO(address)", "0xd644e356": "index(uint256,address,uint256,uint256)", "0xea27a881": "getMinimumEndowment(uint256,uint256,uint256,uint256)", "0x99a88ec4": "upgrade(address,address)", "0xc8e4acef": "playerByAddress(address)", "0x0b7abf77": "TOTAL_TOKENS()", "0xfb5d7376": "step4()", "0xc0aa18e7": "History()", "0xe2233ada": "smartDoor(address[])", "0xd6006e88": "send(address[],uint256[],uint256)", "0x95671958": "getFileListTail()", "0x16bac350": "overthrow(string)", "0x5cb18a6d": "fipsLegacyRegisterMulti(bytes20[],address,bytes)", "0x60116397": "Registrar(address,bytes32,uint256)", "0x60fe47b1": "set(uint256)", "0x5f8f0483": "buyBankerAgreementFromImporterBank()", "0x4c8cc20b": "toContentID(address,string,string,address,uint256)", "0x45ca25ed": "changeName(address,string)", "0xb21bce4c": "vote(bytes,bool)", "0x334dc700": "CanaryV7Testnet()", "0xc31d0031": "CrowdFundDAO(string,uint8,string)", "0xf3d91708": "isEligibleForUpgrade(address)", "0x0ee07836": "adjustDifficulty(uint256)", "0xf6232556": "Security_GetNumberOfAttemptsToConnectBankAccountToANewOwnerAddress()", "0xb2d37e95": "remove_order(uint32)", "0x691d58e7": "_applyRefund(uint256)", "0x1c2353e1": "isCertifier(address)", "0xcf158fe9": "scheduleTransaction(uint256,uint256,uint256)", "0x5d96ec65": "setAdministrator(address,string,bool)", "0x0651844e": "activateBalance(address)", "0x217311ac": "getWords(uint64)", "0xc127c247": "addMember(address,string)", "0x40c0bcb9": "checkBetNumber(uint8,address,bytes32,bytes32)", "0xb633620c": "getTimestamp(uint256)", "0x5b764811": "_jMul(uint256,uint256,uint256,uint256)", "0xfe029156": "swap(address,address,uint256,uint256)", "0x31db4b95": "doTriggerAuth()", "0x203c03fa": "Coinflip()", "0x209a5b8a": "moneySumAtSettlement(address,uint256,int256,uint256)", "0xf10ae2ab": "__dig_then_proxy(uint256,address,bytes)", "0xd532e481": "activateFrozenAccount(address)", "0xe9a9c1b4": "get_party1_balance()", "0x8fcc9cfb": "setMinDeposit(uint256)", "0xe5c7e509": "testThrowTransferDisableNotEnabled()", "0x4e077f2a": "addGasEther()", "0xb7c93330": "ResourcePoolTester()", "0x82661dc4": "splitDAO(uint256,address)", "0x0e554bd8": "scheduleCall(bytes,uint256,uint256,uint8)", "0x49041903": "getGame(uint64)", "0x0e1da6c3": "claimTimeout()", "0xc53ad76f": "Kardashian()", "0x8b7bcc86": "numWinners()", "0x1043dcdf": "LastIsMe(uint256,uint256)", "0x6cd22eaf": "updateAuthority(address,bool)", "0xb796a339": "addRegistryIntoOwnerIndex(address,address)", "0x308d6613": "getSignData(uint256,uint8)", "0xed88c68e": "donate()", "0xb719d1d0": "getRegInfo(address)", "0xac8d6030": "removeRequest(address)", "0x46f0975a": "signers()", "0x434cb64c": "startNextGeneration()", "0x6cb3d30a": "triggerTryAuth()", "0x3c067945": "fundBalance()", "0x26c7edaa": "flip4(bytes)", "0xf76f950e": "uint2str(uint256)", "0x860e9960": "BetPriceLimit()", "0xb0ecca8f": "LookAtLastTimePerZone(uint256)", "0xa35cfa22": "make_move(uint256,uint8,uint8,uint8,uint8)", "0x3f74fecb": "DSTrueFallbackTest()", "0xdd2ad311": "scheduleCall(bytes,uint256)", "0x0ae5e739": "grantAccess(address)", "0x7d5fec5a": "setOwner(uint8,uint8,address)", "0x6a4b6aa5": "untrustedChildWithdraw()", "0x332f93a9": "nextPayoutGoal()", "0xc5ae6e0e": "Kernal()", "0x75438e49": "fillGas()", "0x51404cbe": "forceDivestOfOneInvestor(address)", "0xeacfc0ae": "Authorized()", "0xe59d843a": "Replicator(bytes,uint256,uint256,address)", "0xf00e8651": "createRequest(address[2],address,uint256[11],uint256,bytes)", "0x02acdb44": "setAnyoneCanCall(address,bytes4,bool)", "0x2a24f46c": "auctionEnd()", "0x7ef1925b": "getShareRange(uint256,uint8)", "0x2fac1a54": "newOrder(bool,uint256,uint256,uint256,uint256)", "0x56b8c724": "transfer(address,uint256,string)", "0x33fd066d": "doBalanceFor(address)", "0xf29617da": "registrationDeposit(address)", "0x2b297f9e": "registerDao(address)", "0x79cce1c5": "getReleaseHashes(uint256,uint256)", "0xbed1b8b9": "convertToInt(string)", "0xef5daf01": "_dumpToCompany()", "0x23dc42e7": "query1(uint256,string,string)", "0xa53b1c1e": "setInt256(int256)", "0xb8cf14e7": "updateStatusPlayer()", "0x61aa8d93": "processFee()", "0x10f41715": "updateMintingData(uint256,uint256)", "0x048e2e94": "getAccountSize(address,uint256)", "0x7c47965e": "isInCurrentGeneration()", "0x420a8ac8": "NanoPyramid()", "0xe56556a9": "getPlayerID(address)", "0x5cd2f4d3": "approve(address,bytes32)", "0x8da4d776": "newCommune(address)", "0x4d30b6be": "balanceOf(address,bytes32)", "0x4a606c53": "_db()", "0x4956eaf0": "deploy(address,uint256)", "0xf1fe42b8": "TransactionRequest(address[3],address,uint256[11],uint256,bytes)", "0x63e38ff3": "id_for_nym(uint256)", "0x0e757a2e": "testSetAndGet()", "0x3facd57c": "registerBill(uint256,address,address,uint256,uint256,uint256)", "0xe548cf13": "betOnColumn(bool,bool,bool)", "0x2f1e4968": "makeNewProposal(string,uint256)", "0x0b467b9b": "revoke(bytes)", "0x74bfb965": "addNewProxy(address)", "0x02de2cf3": "isLatestPreReleaseTree(bytes32,bytes32)", "0xfc1f7652": "_isBoardMember(address)", "0xefef39a1": "purchase(uint256)", "0x3ae9b510": "getLatestMajorTree(bytes32)", "0xc24924d6": "setQueryFee(uint256)", "0x839930ba": "getMinimumBet()", "0x8f5e9ca7": "acceptTOS(address,bool)", "0xd1100691": "BookCafe()", "0x839849c0": "changeBaseMultiplier(uint256)", "0x758971e8": "ownerTakeProfit(bool)", "0x2b785960": "testBitAndSuccess()", "0xd96a094a": "buy(uint256)", "0x379607f5": "claim(uint256)", "0x88e072b2": "checkTransfer(address,uint256)", "0x05fefda7": "setPrices(uint256,uint256)", "0xfc63d4fb": "order(bool,uint32,uint128)", "0x5718b994": "checkEvent(address,bytes,bytes,uint256)", "0x0c0662a8": "getLastWithdrawal()", "0xeb947f19": "ExampleResourcePool()", "0xb51c4f96": "getCodeSize(address)", "0x702fc7da": "ReviewModel()", "0xc6cb7a96": "orderMatchTest(uint256,uint256,int256,uint256,uint256,address,address,uint256,int256)", "0xb7760c8f": "transfer(uint256,address)", "0x32b12eac": "setFallback(address)", "0x0a4d564c": "TieUpLooseEnds()", "0xc3ad5ecb": "getTweet(uint256)", "0xe86afde0": "description(uint64)", "0xd0549602": "scheduleTransaction(address,uint256,uint256,uint256)", "0xbf2e694f": "getPreviousRequest(address,address)", "0x2525f5c1": "cancelBid(address,bytes32)", "0x19f02ceb": "set(address,address,uint256)", "0xf00acc47": "prepareRoll(uint256,uint256)", "0x29d28aad": "Broker(address)", "0x041d0c0b": "MyTokenLoad(uint256,string,uint8,string,address)", "0xd81ab0c1": "invoke(uint256,address,address,bytes)", "0xab09ee80": "respond(uint256,uint256,uint256,uint256)", "0xd985f122": "RelayToolsTest()", "0xbe0638e4": "WealthShare()", "0x5263ba87": "getLatestPatchTree(bytes32,uint32,uint32)", "0xb7bae9b7": "exists(bytes,bytes)", "0x0b80f8d3": "invmod(uint256,uint256)", "0xbb4d7cd1": "tag(uint256,string)", "0xadf54e0c": "betOnLowHigh(bool,bool)", "0xed54746e": "lastAuction()", "0xf158458c": "getMinimumEndowment(uint256,uint256)", "0x5fcc2edb": "IndividualityTokenRoot(address)", "0x7cc48875": "Slots()", "0x2885b593": "extractMasterKeyIndexLength()", "0x8940aebe": "publicKey(uint256)", "0x0aece23c": "getFeeAmount(int256)", "0x72c3015c": "mint(int256,address,string)", "0xd6a619e3": "transferIfPuritanical(address)", "0xe30443bc": "setBalance(address,uint256)", "0x1277e24f": "payOneTimeFee()", "0xb958a5e1": "getPhoneByAddress(address)", "0x4e71d92d": "claim()", "0x3e0d4f4a": "ApproveContractorProposal()", "0x18160ddd": "totalSupply()", "0x150ad2a8": "owner_transfer_ownership(address)", "0xa2b5591c": "oraclize_query(uint256,string,string[],uint256)", "0x8d227fc0": "getPeriodInfo()", "0x1c0b6367": "processTransaction(bytes,uint256)", "0xf245b9e4": "DVIP(address)", "0x392327b5": "owner_set_fraction(uint256)", "0xadaccd74": "getNickname(address)", "0x2e0ef395": "voteOnNewEntryFees_only_VIP(uint8)", "0x89c19ddb": "concat(string,string)", "0xcef8d343": "buyShare(uint256,bool)", "0xd224118f": "PrepareDraw()", "0x4269d8ef": "_safeSend(address,uint256)", "0xda1441cd": "KudosBank(uint256)", "0x7ccfd45a": "removeSubUser(address)", "0xcc70bb1a": "publish(string,string,string,address)", "0x708f29a6": "getTotalPayments()", "0x05459f42": "WeeklyLotteryB(address)", "0x452d44dc": "checkBothNotNull()", "0x659fb968": "getOracleOutcomes(bytes32[],address[])", "0x3570c2ee": "PosRewards()", "0xbca86986": "testSetup()", "0xff49b26e": "createEvent(uint256,uint256,uint8,uint32,address,uint256,uint8)", "0x541d920c": "commit(bytes,string)", "0xa6a20ff6": "DSEasyMultisig(uint256,uint256,uint256,uint256)", "0x0f5381f1": "testUserCanIncreaseVersionNumber()", "0xf8f46b5f": "getCurrentMinerAddress()", "0xfcfff16f": "open()", "0x5a9b0b89": "getInfo()", "0xb8017221": "get_party2_balance()", "0x514dcfe3": "seller_accept()", "0x2004dff6": "Basics()", "0x0b6d8d52": "createDAO(address,uint256,uint256)", "0xf18d20be": "adminWithdraw()", "0x8f9df278": "newEntry(int256,bool,uint256,int256,string,bytes32,address,uint256[])", "0x75949c13": "sendHalf(address)", "0x64ac2c4a": "WavesPresale()", "0x8946d33f": "SplitterEthToEtc()", "0x11400d8e": "priv_fastGetBlockHash__(int256,int256)", "0x7266f4a4": "X3()", "0xb189ad2a": "testErrorUnauthorizedAfterTransfer()", "0x31c2bd0b": "propose(address,bytes,uint256)", "0x100c8ada": "setCAmodulus(bytes)", "0x296ed88f": "testFailControllerInsufficientFundsTransferFrom()", "0xd5dbb1ad": "solveBet(address,uint8,bool,uint8,bytes32,bytes32)", "0x8a9ffb90": "transfer(string,string,bool)", "0x968908a3": "createMarketMaker(uint256,uint16,uint256)", "0x7b02b2c9": "sendMsg(address,string)", "0xa33dd801": "setTreasuryBalance(uint256)", "0x2f553d31": "isCreated(bytes32)", "0xf712d7ff": "testFailControllerTransferFromWithoutApproval()", "0xe51ff1fc": "iterateOverThings()", "0x60fd902c": "gnosisToken()", "0x2ef875fb": "div10(uint256,uint8)", "0x640f244b": "findSuitableGen()", "0x16cb9a01": "assertFalse(bool,bytes)", "0xe671f510": "onEtherandomExec(bytes32,bytes32,uint256)", "0x758b5172": "setPlayersPerRound(uint256)", "0x6423db34": "Reset()", "0x21958a50": "AddressSeries(address)", "0xfb87d5ea": "TransactionRequest(address[4],address,uint256[11],uint256,bytes)", "0xfb279ef3": "tip(uint256,address,uint256)", "0x338cdca1": "request()", "0x4e7ad367": "fireEventLog1Anonym()", "0xbd9335c0": "scheduleHangouts()", "0x4cb85356": "BranchSender(uint256,bytes32)", "0x1d7e1f68": "getContentRank(address,uint256)", "0x1a1df394": "Play(bool)", "0x468129a5": "setUnit(uint256,uint256,uint256)", "0xecb70fb7": "hasEnded()", "0x2d49ffcd": "getLocked()", "0x2e06c756": "post(string,string,string,uint256,uint256,address)", "0x73f93a48": "getAccountContentTip(address,uint256)", "0xf6a3d24e": "exists(address)", "0x5fbddcf3": "isLivingMonarch()", "0x6d568c43": "weiToCents(uint256)", "0xacf4280c": "buildDSApprovalDB()", "0xf3541901": "execute(address,bytes,uint256,uint256)", "0x88eb7af7": "_isHuman()", "0x48a490fb": "transferFromTreasury(address,uint256)", "0x5e03d393": "setAccountFrozenStatus(address,bool)", "0xfc687311": "betOn(int8)", "0x5bbfe9b6": "_myGroupHelper()", "0x5629c6d9": "doExecution(address)", "0xe3a9b508": "EnableDisableTokenProxy()", "0x9229c504": "new_mainPlayer(address)", "0x6f6c0244": "generateShortLink()", "0x33613cbe": "getBondBalance(address)", "0x4229616d": "collectPercentOfFees(uint256)", "0x4ed3885e": "set(string)", "0x043bb5e7": "getIdentities(address[])", "0xad2fea7c": "removeMinter(int256,address)", "0x0b7e9c44": "payout(address)", "0x17f5de95": "MAX_TOKENS_SOLD()", "0x50ea1932": "lookupISO3116_1_alpha_2(bytes)", "0x96f7807a": "getDuel2(uint256)", "0xa97ffd5e": "safeToSell(uint256)", "0x2f4ee5d4": "registerThrone(bytes,uint256,address,uint256,uint256)", "0x4c0bcfe5": "getTransferableBalance(address)", "0x0d17bc2e": "_disallow()", "0x0ca7395f": "returnFund(address,uint256)", "0x69fe0e2d": "setFee(uint256)", "0xfaf27bca": "greeter(string)", "0x0c7de59d": "edit(address,bytes,bool)", "0x16e27349": "getFeeRecipient(int256,int256)", "0x37751b35": "doTransfer(address,address,uint256)", "0x67fc1c6a": "validateProposedMonarchName(string)", "0xf59f99ee": "createNextGeneration()", "0x6be505f5": "selectWinner(bytes32)", "0xf6bd5893": "getGas(uint256)", "0x35b09a6e": "someFunction()", "0xb3aaa277": "validate(address[4],address,uint256[11],uint256,bytes,uint256)", "0x4f052648": "XaurumDataContract()", "0x117b4705": "retract(bytes32)", "0x2145e36c": "testBitSetFailIndexOOB()", "0x3d750b28": "found()", "0x1334a5e2": "eventCallback(uint8,address,address,uint256)", "0x3c2c21a0": "scheduleCall(address,uint256,bytes4)", "0x82996d9f": "rent()", "0xaf640d0f": "id()", "0xdaf22f4d": "identify(bytes32)", "0xfe4667e9": "getMaxLossAfterTrade(address,uint256,uint256,int256,int256)", "0xfc108f70": "GamblerPerAddress(address)", "0x89f4ed7a": "getLastTag(uint256)", "0xfcc11241": "addOrder(uint256,uint256,uint256,uint256,uint256,uint8)", "0x43243797": "fundsOf(address)", "0x892c0214": "NumberOfCurrentBlockMiners()", "0xb5a6c525": "extractFrozenAccountLength()", "0x1acb2719": "getNextRequest(address,address)", "0xa89a4f09": "creatorBalanceChecker()", "0x1e83409a": "claim(address)", "0x5e1d7ae4": "changeFeeRebate(uint256)", "0xb7482509": "deposit(address,string)", "0xfb47a067": "_getRevisionBlockNumber(bytes20,uint256)", "0x5dcdddd1": "testSafeToAddFix()", "0x9aa26f06": "registerBytes32(address,bytes)", "0xd085e66e": "GetPart(bytes32,uint256)", "0x2cd78450": "activateExportFeeChargeRecord(address)", "0x35d129f6": "untag(string)", "0x1a7a98e2": "getDomain(uint256)", "0x877653f0": "_storeBalanceRecord(address)", "0x446fbcd1": "CredSign()", "0xfae8f9a2": "setInitialParent(int256,int256,int256,int256,int256,int256)", "0xc1b056b0": "getNodeLeftChild(bytes)", "0x71f297cc": "XaurumToken(address)", "0xe3ffc9a3": "sendEtherToOwner()", "0xeccf1b29": "CrystalDoubler()", "0x57f4d5ec": "processDividends(address,uint256)", "0x75c589a0": "getMinimumCallCost()", "0x66772438": "computeResponse(uint16)", "0x7fefde53": "WillRegistry()", "0x8f4fb958": "calculateRandomNumberByBlockhash(uint256,address)", "0xed498fa8": "userTokens(address)", "0x5601eaea": "execute(uint256,uint256)", "0x8dd8596c": "sendDonation()", "0x15a0df43": "testThrowCreateNewRevisionNotOwner()", "0x0382c254": "CheckHash(uint8,uint8,uint8,uint8,bytes32)", "0x157f8f51": "feePaid(int256,int256,int256,int256)", "0xf00aac7f": "ArrayRR()", "0x7b7d7225": "_approve(address,uint256)", "0x54ed7b6e": "addHash(bytes)", "0x235c002d": "transferOther(address,address,uint256)", "0x7057c20d": "CFD(address)", "0xd5563f31": "createAuction(uint256)", "0x46c3166f": "testThrowRetractLatestRevisionNotOwner()", "0x4420e486": "register(address)", "0x9a969768": "distributeProfits(uint256)", "0x464f37c9": "trustedChildRefund()", "0x5d495aea": "pickWinner()", "0xdf55b41a": "owner(string)", "0x10e6e06c": "vote(bool,uint256)", "0xe7faecec": "testFailInsufficientFundsTransfers()", "0xea98e540": "proxyTransferFromToICAPWithReference(address,bytes32,uint256,string)", "0xfff78f9c": "doThrow()", "0x9bb01b5f": "ElcoinDb(address)", "0xdc6dd152": "playerRollDice(uint256)", "0x5d0be9de": "softWithdrawRevenueFor(address)", "0xcd591822": "CanaryV7Fast()", "0x36e6b92e": "taskProcessedWithCosting(uint256,uint256)", "0x7bb6a4c6": "uno(uint256)", "0x03427656": "getDefaultSoftResolutionBlocks()", "0xc1fd4339": "createMarket(bytes32,uint256,uint256,address)", "0xeb95b7d5": "Bounty(address,address)", "0x4dd49ab4": "get(bytes,uint256)", "0xfa6d373c": "LeaderHash()", "0x8c8d98a0": "toTimestamp(uint16,uint8,uint8)", "0x9a79f4a8": "testFailHeaderInsufficientFee()", "0xdd90c403": "getAccountFeed(address,uint256,uint256)", "0x58e59c32": "get_entry(uint256,uint256,uint256)", "0xfd747c0b": "rsaVerify(bytes,bytes,uint256,bytes)", "0x0f4cf692": "numMessages()", "0x18433bb7": "DrawPrepare()", "0x1dea0c57": "getRealBet(uint256)", "0x7d60e343": "getFileListSize()", "0xd24ddcfe": "buyKissBTC()", "0xa055fe64": "_projectCommitNew(address)", "0x5f17114e": "TimeDeposit()", "0x85fe0448": "testThrowRestartNotUpdatable()", "0x901717d1": "one()", "0x528fd7b0": "manualPayExpiredDuel()", "0x85952454": "newOwner(address)", "0xf34c7010": "commitSecurity(address,uint256,uint256)", "0x3bf2313d": "__transferToICAPWithReference(bytes32,uint256,string)", "0x67b830ad": "fillOrder(uint256)", "0x73fac6f0": "confirmReceived()", "0xf1b3f968": "getRaceEndBlock()", "0xf99fc046": "dEthereumlotteryNet()", "0xb409da05": "logDoubleIndex(bytes,bytes,bytes,uint256)", "0x9e920587": "testOwnedAuth()", "0x8e7cb6e1": "getIndex(uint256)", "0xe2f8a017": "payInstallment(uint256)", "0xac3e6b2f": "testSetNotRetractable()", "0x8fbc3ecd": "BUFFER()", "0x4b729aff": "buyNumber(uint256)", "0x166c4b85": "len(bytes32)", "0x6299f8cf": "stop(uint256)", "0xd767aee0": "bbb()", "0x29090202": "Resolver(address)", "0xcc2c2bcf": "MotionFactory(string,string,string)", "0xfd260dfc": "getCertificationDbStatus(address)", "0x30aceb89": "validateRequestParams(address[3],address,uint256[11],uint256,bytes,uint256)", "0xd11f13df": "numberOfParticipantsWaitingForPayout()", "0xd05c78da": "safeMul(uint256,uint256)", "0x69953501": "setUtils(address)", "0xff7f5f2a": "EtherizationUtils2()", "0xde4b3262": "setBasePrice(uint256)", "0x6cf9cc58": "registerResource(bytes,uint256,bytes,string)", "0x7ac37d58": "ownerTransferEther(address,uint256)", "0x0121b93f": "vote(uint256)", "0x07b6f631": "testTestHarnessAuth()", "0x869b3f6a": "testThrowsRetractNotOwner()", "0x18253234": "ticketsAvailable()", "0x5581004d": "createThrone(bytes,uint256,uint256,uint256,uint256)", "0x7102c138": "Standard_Token(uint256)", "0xce5566c5": "cash(uint256,uint256)", "0x16a25cbd": "ttl(bytes32)", "0x0c9fcec9": "setApproval(address,address,uint256)", "0xe6d95eb8": "DSAuthorized()", "0x34c0d654": "setPackageDb(address)", "0x0ee79fb3": "closeReferendums()", "0xe2cdd42a": "vote(uint256,address,bool)", "0xd3f297d6": "claimLiquidityReward()", "0xf37b437b": "scheduleCall(address,bytes,uint256,uint256,uint8,uint256,uint256)", "0x2090cf8b": "consultBalance(address)", "0xc9296d14": "scheduleTransaction(address,uint256,uint256,uint256,bytes)", "0x7993e5c2": "Devcon2TokenForTesting()", "0x268bb78e": "propose(address,bytes,uint256,uint256)", "0x2b16b56a": "setIndex(uint256,uint256)", "0x6d15f208": "reject(string,uint256,uint16,address,uint256)", "0xc4ff3614": "Wallet(address[],uint256,uint256)", "0xb47d89ad": "Details()", "0x0ae08793": "confirmAndCheck(bytes32)", "0x061e494f": "getBet(uint256)", "0x314e99a2": "abdicate()", "0xe487eb58": "getOwner(bytes20)", "0x7ee65635": "LookAtDepositsToPlay()", "0x9e9d3aa4": "FirstBloodToken(address,address,uint256,uint256)", "0x4dfd1b02": "setUint8(int8,uint8)", "0x82fbdc9c": "register(bytes)", "0xa8b60b93": "ackMsg(uint256,string)", "0x081e806d": "PayOut(uint256)", "0x8bab8791": "testPkgUpdate()", "0xc262df45": "isKnownRequest(address,address)", "0x4123cb6b": "m_numOwners()", "0x62be3172": "Message(address,address,address,string)", "0x0d368fee": "deverify(address)", "0x5f1231ea": "getMemberInfo(address)", "0xa07daa65": "newRequest(uint256)", "0xa4406bcd": "placeSellOrder(uint256,uint256)", "0x5b7d47a9": "betOnColor(bool,bool)", "0x9c6034a7": "sendIfNotForked()", "0x26a4861c": "CROWDFUNDING_PERIOD()", "0xbaac5300": "createTokenProxy(address)", "0xfc72c1ef": "ERC20Base(uint256)", "0x316b08a0": "scheduleTransaction(address,bytes,uint256[7],uint256)", "0x0b590c6b": "SingularDTVToken()", "0x750e443a": "voteAgainst(uint256)", "0xfc7b9c18": "totalDebt()", "0x7ff9b596": "tokenPrice()", "0xd67cbec9": "release(uint32,uint32,uint32,bytes20)", "0x553cc48d": "Player(string)", "0x579cdf17": "getAdminName(address)", "0x7e1c4205": "query2(uint256,string,string,string,uint256)", "0x54107401": "declareLove(string,string)", "0xea4ba8eb": "getOutcome(bytes)", "0x9b5adea2": "setMinter()", "0x185061da": "undoIt()", "0x90c3a370": "AuctionMaster()", "0xbd02e4f6": "calcRandomNumberAndGetPreliminaryGameResult(uint256,uint64)", "0xbc126ba1": "totalCents()", "0xa3747fef": "register(bytes,bytes)", "0x805210b7": "AmIPlayer2()", "0x4e05ded6": "ClassicCheck()", "0xec2ac54e": "deposit(address,uint256,bytes32,uint256)", "0x49c15bd9": "Purchase()", "0x87bb7ae0": "getTicketPrice()", "0xf2f03877": "commit(uint256,bytes32)", "0x167d3e9c": "SetOwner(address)", "0x5c634241": "CanaryV6()", "0xba15e52e": "getInfo(bytes20)", "0x06c1df7b": "checkBetColumn(uint8)", "0xf7bc39bf": "owns(address)", "0x27b752b8": "sha3HexAddress(address)", "0x5ac801fe": "setName(bytes32)", "0x1ae460e5": "isInPool()", "0x85c7a953": "WithdrawFullBalanceFromBankAccount()", "0x755b5b75": "setNumUnits(uint256,uint256)", "0xaefc8c72": "unsealBid(bytes32,address,uint256,bytes32)", "0xfab43cb1": "getPongAddress()", "0x9e997121": "getConfigAddress(bytes)", "0xda2b7416": "testBitsAndFailIndexOOB()", "0xd0e30db0": "deposit()", "0x4a0d89ba": "getSwap(uint256)", "0x63a9c3d7": "verify(address)", "0x337b1cf9": "setIpfsHash(bytes)", "0xbaf00f76": "removeAllSubUsers()", "0x1b370abb": "getPreviousNode(bytes)", "0x741e2345": "registerMany(address,uint256,int256,uint256,bytes20,address,bytes)", "0xb3760c80": "orderMatch(uint256,uint256,uint256,int256,uint256,uint256,address,uint8,bytes,bytes,int256)", "0x73ffd969": "setMap(uint256,uint256,uint256)", "0x50b44712": "tickets(uint256)", "0x6a9d2afd": "playToWinTest(uint256)", "0x644998ae": "maintain(int256,uint256,uint256)", "0x2203ab56": "ABI(bytes32,uint256)", "0x9c67f06f": "registryStarted()", "0x93423e9c": "getAccountBalance(address)", "0x524e4e61": "testDistribution()", "0xa17042cc": "getMsgValue()", "0x4f9d719e": "testEvent()", "0xcbcaacab": "checkTransferWithReference(address,uint256,string)", "0x1bcf5758": "getOccupies(uint8)", "0x1d4b0796": "updateTxStats()", "0xd173707d": "hasPhysicalAddress(address)", "0xa54a2b8b": "testBlockHashFetch()", "0xc51bf934": "CEILING()", "0x0e54b872": "registerUser(string,address)", "0xba51a6df": "changeRequirement(uint256)", "0x6bd92f7c": "activateAllowanceRecord(address,address)", "0x90daaf67": "getMinimalDeposit()", "0x85d5c971": "logTransfer(address,address,bytes32)", "0x8b9e5385": "MeterSlock(uint256,uint256,address)", "0x7bcd7fad": "getRecordAtIndex(uint256)", "0x8e035ac1": "BetOnHashV82()", "0x5dbe47e8": "contains(address)", "0x21bb79fe": "luckyDogInfo()", "0xdd467064": "lock(uint256)", "0xa4beffa7": "increaseInvestment()", "0x8400c307": "isRecipientAllowed(address)", "0x0965bf7d": "processProposals()", "0x4b5dc8cb": "roundMoneyDown3SFExt(uint256)", "0x777feff5": "getCertificationDbAtIndex(uint256)", "0x9462eae5": "ChangeContractor(address)", "0x4c6d1d9e": "checkOutTag(string)", "0x09a399a7": "personAdd(string,int256,int256,string)", "0x31380c89": "TokenSale()", "0xd0bff051": "testSetBalanceDb()", "0x4c738909": "getMyBalance()", "0xb2310cc5": "payRequstedSum(uint256,uint256)", "0xda3c300d": "currentFee()", "0x6ed7c013": "move_monsters()", "0x4f073130": "takeOrder(bool,uint256,uint256)", "0x6860fd58": "Fees(uint256)", "0x214c9d50": "WritedrawStatus()", "0xf314bf46": "setReleaseDb(address)", "0x561a4873": "buyAd(string,string,string,uint256,uint8,address)", "0xf249cf19": "get_all_challenges()", "0xbbed7177": "getContentTimestamp(uint256)", "0xc864e760": "recordCommissionEarned(uint256)", "0x1896f70a": "setResolver(bytes32,address)", "0xfd35e71b": "entryPayoutDue(uint256)", "0x5a58cd4c": "deleteContract()", "0xb29b5366": "setRentable(bool)", "0xad5c613d": "purchase(bytes)", "0x6949a058": "sendOwnerEther()", "0xc03e382f": "calculateShare()", "0xf5bade66": "setDeposit(uint256)", "0x384e5018": "etherandomCallbackAddress()", "0xf06186c7": "testReality()", "0x677342ce": "sqrt(uint256)", "0x10e89b22": "remove_deal(uint32)", "0xf2b445ad": "rowround(uint256,uint256)", "0xd7ed7453": "redeemWinnings(uint256)", "0x92b4bb50": "rps()", "0x089327de": "MyToken()", "0x87ebd76c": "initContract(string,string,uint256,uint256)", "0xdbc45228": "newProposal(address,uint256,bytes,bytes)", "0x6b1feeeb": "get_my_sig()", "0x6837ff1e": "newContract(address)", "0x9f181b5e": "tokenCount()", "0x92ba4ba6": "GridMember(string,uint256,bool,address,address)", "0x45755dd6": "returnFunds(uint256)", "0xb4b9d1f1": "lookup(uint256,uint256)", "0x98024f18": "testThrowsTransferDisableNotEnabled()", "0x9e7b8d61": "giveRightToVote(address)", "0x8112821f": "EthVentures()", "0xe65d6b49": "getCommission()", "0x068c966b": "DrawDetails(uint256)", "0x9bb5239a": "CheckPrize(address,uint256)", "0xff08d2b0": "PayMiners()", "0x9be1fcee": "BankOwner_DisableConnectBankAccountToNewOwnerAddress()", "0x5f972df8": "_jDiv(uint256,uint256,uint256,uint256)", "0xfe8b6642": "setEnforceRevisions(bytes32)", "0xe4cc1161": "seedWithGasLimit(uint256)", "0x5fd4b08a": "getName(address)", "0xaa51793c": "isLosingBet(uint256)", "0x31757f2e": "collisionCount()", "0xe1f5ebc5": "_projectAddNew(address,uint256)", "0x64228857": "getRevisionCount(bytes32)", "0x5ca3400c": "WithBeneficiary(address)", "0x39f4debc": "fillOrderAuto()", "0xcc2c5453": "add_sword(uint16)", "0x0a19b14a": "trade(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32,uint256)", "0x4d207d9a": "identify(address)", "0x3e83fe36": "getMyShares()", "0x8bb0faee": "setRef(string,string)", "0x38bbfa50": "__callback(bytes32,string,bytes)", "0xcee6f93c": "getResultOfLastFlip()", "0xbbba3333": "safer_ecrecover(bytes32,uint8,bytes32,bytes32)", "0x8e19899e": "withdraw(bytes32)", "0xd8389dc5": "hash(bytes32)", "0x8a4068dd": "transfer()", "0xea9e107a": "acceptRegistrarTransfer(bytes32,address,uint256)", "0x8f4ffcb1": "receiveApproval(address,uint256,address,bytes)", "0xf67abd87": "entryDetails(uint256)", "0x67acd805": "lowerMinWager(uint256)", "0xec035393": "_getAllRevisionBlockNumbers(bytes20)", "0xbd858288": "orderMatch(uint256,uint256,int256,uint256,uint256,address,uint8,bytes32,bytes32,int256)", "0x112c7075": "ManualDeposit()", "0xd81a91e9": "get_party2()", "0xc52bd836": "setDappOwner(bytes32,address)", "0xf84f420b": "getRandomNumber(address,uint256)", "0xcfe9a7b8": "getPackageName(uint256)", "0xe97dcb62": "enter()", "0x48db5f89": "player()", "0x6bdbf8e6": "concat()", "0x3c959aca": "CheckTickets()", "0x3aa5f4f7": "changeTokenSettings(uint16,uint256,uint256)", "0xac20902e": "NormalizeMoney()", "0x2fac1d36": "isReadyFor(address)", "0xdcaa5620": "findNextWeekday(uint256,bytes)", "0xf9909915": "bulkStoreHeader(bytes,int256,bytes,int256)", "0xcd2cdd5b": "claimOwnershi()", "0xcfae3217": "greet()", "0xf5c8d71b": "forceMove(address,address,uint256)", "0x9718b524": "newTreasury(address)", "0xd0679d34": "send(address,uint256)", "0x1301ee02": "transferringETC(address)", "0x60eb2826": "Badge()", "0x0d0c2008": "TwoAndAHalfPonzi()", "0x17e1bfb7": "addInstitution(address,string)", "0x06394c9b": "changeOperator(address)", "0x80c951bf": "currentClaimPriceInFinney()", "0xd063f55f": "toLittleEndian(uint64)", "0x53f818d6": "checkBetValue()", "0x9205fbc2": "testAuthorityAuth()", "0x3e4c0c82": "player_1(uint256)", "0xe571c35e": "ReverseRegistrar(address,bytes32)", "0x24804cef": "Deed()", "0x622e88cb": "testBitsXorSuccess()", "0xdfca2f53": "LookAtPrizes()", "0xafa293d4": "getSource()", "0x755f99c2": "AddNewSmallContract(address)", "0x7137ed47": "setProxyContract(address)", "0x835b42fc": "testThrowUpdateLatestRevisionNotUpdatable()", "0xdd34e129": "PriceTest()", "0xedb27f4e": "switchWizard(address)", "0x1c5d9faa": "setNickname(string)", "0x4746cef8": "_confirmAndCheck(address,bytes32)", "0x189c94ae": "testFallbackStaticSig()", "0x0cb749b6": "FutureBlockCall(address,uint256,uint8,address,bytes,bytes,uint256,uint256,uint16,uint256,uint256)", "0x2b25a7e4": "giveKudos(address,uint256)", "0x294f3d4d": "setUpLimit(uint256)", "0x2cce81aa": "getBlockHash(int256)", "0x4cd11943": "NewManualInvestor(address,uint256)", "0x7eaef50c": "over()", "0x8ac4e1d8": "TemperatureOracle()", "0xf108a7d2": "withdraw(uint256,address,string)", "0x00a676f9": "getExists(bytes32)", "0xb8d4efb5": "validate_percent(uint8)", "0xc7489441": "closeMarketMaker(uint256)", "0x3def449b": "FipsNotary()", "0x5687f2b8": "emitApproval(address,address,uint256)", "0xa9f8ec6c": "AlarmClockTipFaucet()", "0xd8e5c048": "scheduleCall(address,uint256,uint256)", "0x135217e7": "requires_depth()", "0x0aa46c12": "testClearBitFailIndexOOB()", "0x77d32e94": "ecrecovery(bytes32,bytes)", "0xace523c4": "createReferendum(string,string,uint256,uint256)", "0x5ca8bc52": "returnIt()", "0xdb318833": "_ecAdd(uint256,uint256,uint256,uint256,uint256,uint256)", "0x623195b0": "setABI(bytes32,uint256,bytes)", "0xd7bb99ba": "contribute()", "0x2880ebe7": "underdogPayoutMarkup()", "0x4ce01d86": "totalBetValue()", "0x837a7ba5": "testThrowTransferDisabled()", "0x386fcda8": "testCreateCostToken()", "0x0e850239": "scheduleCall(bytes4,bytes)", "0x163aba3c": "getQueryFee()", "0x9941e3d0": "setCallAddress(address)", "0x23637e60": "votePrice(uint256,bool)", "0xde78e78a": "tokenLaunched()", "0xe3579ea5": "publish(string,string,address,uint256)", "0x59a547b0": "recordCommission(uint256)", "0x1aa86370": "updateXIPFSPublicKey(string)", "0x97fcb54e": "transfer_eth(address,uint256)", "0x05d2f92a": "check_depth(address,uint256)", "0xdfcbb794": "TrustFund(address,uint256,address)", "0xb7dd1d17": "getAllRevisionBlockNumbers(bytes32)", "0x75862df4": "TokenWithEStop(address)", "0xd22057a9": "register(bytes32,address)", "0x29d017b5": "TestWithConstructor(address,uint256[])", "0xd216d55d": "etherandomExec(bytes32,bytes32,uint256)", "0xfba06849": "fipsPublishDataMulti(bytes20[],bytes)", "0xa37fd390": "setHomeAdv(uint256,string)", "0xcf2e3efc": "GetBankAccountBalance()", "0x423e7e79": "_dispatchEarnings()", "0x74087040": "testBitsNotEqualSuccess()", "0x61d585da": "state(bytes32)", "0xcfb3a493": "getMyBounty(uint256)", "0x5afeb106": "Sqrt()", "0xf9e84395": "unexempt(address)", "0x5669c94f": "issueToken(address,string)", "0x19b05f49": "accept(uint256)", "0x3ae01f84": "USDOracle()", "0x8c172fa2": "getEvent(bytes32)", "0x4671e65e": "proposeEmergencyWithdrawal(address)", "0xc27d7721": "create(uint256[101][])", "0x5c52e51e": "processPayout()", "0xf7a0fa0a": "getShareDistribution(bytes)", "0x31a3a506": "closeFunding()", "0x465e759b": "testRestart()", "0xb60d4288": "fund()", "0x52200a13": "getNumHolders(uint256)", "0xf2c298be": "register(string)", "0x7bc25372": "UserCheckBalance(address)", "0x104d5fdd": "getPriceProxy()", "0x447cd682": "scheduleTransaction(address,uint256)", "0xa045fdff": "scheduleCall(address,bytes)", "0x4757f1d2": "redeemAllOutcomes(uint256,uint256)", "0x5e855f14": "Dice(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x5d3c1d4c": "_getRequest(uint256)", "0x416c8701": "beyond()", "0x63aea3e0": "PlayerInfo(uint256)", "0xa163a624": "Test()", "0xedede601": "testBalance()", "0x13651124": "WithdrawAmountFromBankAccount(uint256)", "0x893d20e8": "getOwner()", "0x90b5561d": "insert(uint256)", "0xf9983a12": "GetMyInvestmentBalance()", "0xb71c47a2": "surrender()", "0xf2a75fe4": "empty()", "0x804ba97a": "tryGet(bytes)", "0x6506b623": "rotateBitsLeft(bytes,uint256)", "0x3ef8ec78": "announce_numbers(uint8,uint8,uint8,uint8,uint32,bytes32)", "0x73abecbb": "kill1()", "0xd5171523": "euroteambet()", "0x8e52cb51": "getRecordKey(bytes,bytes,bytes)", "0x7adbf973": "setOracle(address)", "0x4aa16737": "enter(uint8)", "0xf0cb556c": "updateLatestRevision(bytes32,bytes)", "0xbc8fbbf8": "nuke()", "0xc8e55708": "oraclize_query(string,string[1])", "0x7332b520": "getRewardsCount(uint256)", "0xf7c2b38c": "seconds_left()", "0xba344743": "_rawTransfer(address,address,uint256)", "0xcab5c0f1": "_incrementState()", "0xe044c2de": "newLoan(bytes,address,uint256,uint256,uint256,uint256,uint256,uint256)", "0x76abc03b": "getShareDistribution(uint256)", "0xf0da84f8": "getTransferable(bytes32)", "0xcde99727": "calculateROI()", "0x155dd5ee": "withdrawFunds(uint256)", "0x8b543b80": "maximumCredit(address)", "0x340ddda6": "MeatConversionCalculator(uint256,uint256)", "0x524f3889": "getPrice(string)", "0x84054d3d": "cashout()", "0x856f3080": "WhatWasMyHash(bytes32)", "0x0386a016": "closeProposal(uint256)", "0xcebce72d": "token(uint64)", "0x7f480f9d": "processDividends(address)", "0x11d12402": "testEasyPropose()", "0x2f695053": "getCertifierAtIndex(uint256)", "0xd9fcb31f": "comm_channel()", "0x141c4e60": "challenge(uint256,address)", "0x4ff13571": "x2()", "0xa01bc729": "monster_attack(uint256)", "0x2fe9541f": "addIssueBounty(string,uint256)", "0x5503a659": "smallponzi()", "0xdfc765dc": "getMatchers_by_index(uint256)", "0x0b7623ba": "abs(int8)", "0xcde0a4f8": "setRegulator(address)", "0xf95b5a58": "getInitialAnswer(uint256)", "0x66b42dcb": "register(address,string,uint256,string)", "0x9f2ce678": "vote(bytes32,bool)", "0xb3559460": "getGenerationSize(uint256)", "0x5ddae283": "transferRegistrars(bytes32)", "0x59dc735c": "getClient()", "0xc258ff74": "List()", "0x4fb4bcec": "step5()", "0xed684cc6": "trigger(uint256)", "0x09405164": "getOpenCandidates()", "0x5c5d625e": "getProof()", "0x9f5f7c7f": "tokenSplit(address,address,address,uint256)", "0x0e38901a": "unvault(uint256)", "0x75160a20": "pay_royalties()", "0x15398afe": "compareNumericStrings(string,string)", "0xbbd8b602": "getOracleOutcomes(bytes,address[])", "0xebae35a6": "DAOTokenCreationProxyTransferer(address,address)", "0x15abc160": "createValidatedRequest(address[3],address,uint256[11],uint256,bytes)", "0x830953ab": "claimAmount()", "0x26b916b4": "Set_Interest_Rate(uint256)", "0x1fb291cb": "registerInt(address,int256)", "0x505fb46c": "add(uint256,uint256,uint256)", "0xf00d4b5d": "changeOwner(address,address)", "0x034187fd": "setEthToCents(uint256)", "0x94d9cf8f": "CreateProxyWithControllerAndRecovery(address,address[],uint256,uint256)", "0xacbf98a7": "endsWith()", "0xfc2c3e08": "getIteration()", "0x6d7da0b1": "MyContract()", "0x1558ae4d": "Etheroll()", "0x42cbb15c": "getBlockNumber()", "0x29cd62ea": "setPubkey(bytes32,bytes32,bytes32)", "0x2030f721": "num_objects()", "0xbc08afd9": "WebOfTrustToken(address,uint256)", "0x8cdfb1e6": "transferIfHF(address)", "0xa0bd3c0f": "scheduleCall(address,bytes,bytes,uint256)", "0x4e71e0c8": "claimOwnership()", "0xc1cc0775": "calculateFeeDynamic(uint256,uint256)", "0x50c42921": "replicate()", "0x25495998": "getMinimumConsumerDeposit()", "0x3d8e2947": "getFileAddress(bytes)", "0x1f794436": "getBlockHeader(int256)", "0x7d380265": "addOptionChain(uint256,string,uint256,uint256,bytes32,address,int256[])", "0xec0b4153": "getMoneyness(int256,uint256,uint256)", "0x01775f23": "_closeBooks()", "0x9d063ed8": "FIFSRegistrar(address,bytes32)", "0x083b2732": "callback()", "0xa1920586": "offer(uint256,uint256)", "0x19c47214": "getBlockVersion(bytes)", "0xa293d1e8": "safeSub(uint256,uint256)", "0xfe73e3ec": "preliminaryGameResult(uint64)", "0xf004b12b": "CrowdFund(uint256,uint256,address)", "0x54d03b5c": "changeFeeMake(uint256)", "0x9dbc4f9b": "participantDetails(uint256)", "0xd002462b": "setDeploymentFee(uint256)", "0xed2b8e0b": "getPoolRotationDelay()", "0xf697a0ed": "ppb(uint256,uint256)", "0x964c836c": "receiveExecutionNotification()", "0x5e0e2957": "dumpOut()", "0x33232609": "blake2b(uint64[],uint64[],uint64)", "0x88f53db1": "getDataRequest(uint256)", "0x0caf9d39": "testFailTooManyMembers()", "0x1f2e886c": "testControllerTransferTriggersEvent()", "0x586a69fa": "getMaximumStackCheck()", "0xf64fca2e": "getNodeId(bytes)", "0x8b95ec0c": "testAddBalance()", "0x32e7c5bf": "B()", "0x57e6c2f4": "isAuthorized()", "0xb2f2588b": "sortNumbers(uint8[3])", "0xe95bee59": "checkFormat(string)", "0xcef887b0": "storeBlockWithFee(bytes,int256)", "0xbe26733c": "Kill()", "0xe82f7dd4": "testThrowsRetractLatestRevisionNotUpdatable()", "0xfd7ac203": "TestToken()", "0x6d052f56": "testBitsSetSuccess()", "0xf65c4d42": "Participate(uint256)", "0x432c685f": "trustClient(address)", "0xb0171fa4": "getCurrentGenerationId()", "0x03251a08": "setMin(uint256,uint256)", "0x58d3b617": "Notifier(string)", "0x15dacbea": "transferFrom(address,address,address,uint256)", "0x83f7b8e1": "getNumberOfPhotos()", "0xf1076703": "getVerificationId(address,bytes,bytes)", "0x752a3df6": "transferIfHardForked(address)", "0xaf93afdd": "Shipment(bytes,bytes,bytes,bytes,string,bytes,uint256,uint256,bytes,bytes,uint256,uint256,string,bytes,bytes,bytes)", "0x7fcf532c": "Withdrawal(address,uint256)", "0x72ea4b8c": "getNumInvestors()", "0x7df52ba8": "Arbitrate(uint32,uint32,bool)", "0xea25f24a": "TokenCreation(uint256,uint256,address)", "0xf74100e3": "getBits(bytes)", "0x63bfe3d8": "SkillBeatsLuck()", "0x80599e4b": "remove(string)", "0x6c050eae": "look()", "0xf9a794ad": "EtherLovers()", "0x501e8428": "getPart(bytes,uint256)", "0xf446c1d0": "A()", "0x2d67bb91": "World()", "0xeef547d7": "deal_details(uint32)", "0xa6cb9e64": "scheduleCall(address,bytes,bytes)", "0x659010e7": "m_spentToday()", "0x9b0b9c07": "acceptBankDraft()", "0x315e2f1b": "setTestString(string)", "0x69bcdb7d": "getCommitment(uint256)", "0xe1376da2": "updateFirstActiveGamble(uint256)", "0xc70d169d": "answerRequest(uint256,bytes)", "0xa094a031": "isReady()", "0x74e60a48": "cancelOrder(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32)", "0x1b5ee6ae": "mintToken(int256,address,uint256)", "0x7f791833": "toTimestamp(uint16,uint8,uint8,uint8)", "0x5e6ad49d": "_setCosignerAddress(address)", "0xb2e85b67": "getPlayerStatus(address,uint256)", "0x30a24abd": "create(bytes4,bytes)", "0x9f0e3107": "get_timestamp(bytes32)", "0x33397816": "withdrawAccountBalance(address)", "0x0da3e613": "EthFactory()", "0xa5f3c23b": "add(int256,int256)", "0xc1829a14": "testFailTooFewConfirms()", "0x5322f0c5": "getChannelOwner(bytes)", "0x0fd1f94e": "firstClaimBlock()", "0x639d57f2": "testGetBitSuccess()", "0xd3aa22c7": "transferTLA(string,address)", "0x86314af9": "BetOnHashV84()", "0x3059ac30": "Escrow(address,address)", "0x0efafd01": "getPlayerGainLossOnLastFlip()", "0x49bf66d3": "addRegistryIntoNameIndex(address)", "0x3af39c21": "undefined()", "0xb660d77c": "switchMPO(address,address)", "0x0fdb468f": "fee(uint64)", "0x72479140": "CreateTicket(address,uint8,uint8,uint8)", "0xe79a198f": "unregister()", "0x688dcfd7": "setProofType(bytes1)", "0xfb9a4595": "GitHubBounty()", "0xd02a9889": "getDateOfFirstPayment()", "0xca35271c": "numDebtors(address)", "0x08714bfa": "TestContract()", "0x16d960b5": "createThing(bytes32[],bytes32[],uint88)", "0x17961d0f": "ord()", "0x62ba9687": "toTimestamp(uint16,uint8,uint8,uint8,uint8)", "0xbba91ea7": "getHomeadvIndex(uint256)", "0xb45105b2": "post(string,address,string)", "0xa7e25683": "testShortOutput()", "0xa068e8d3": "convict(uint256,uint256,uint256,uint256)", "0x09a69f57": "getRewardAmount()", "0xe7334156": "processNextDeposit(address)", "0x62ee6d29": "changeHashtoLowOrHigh(uint256)", "0xa80d4e9a": "EtherAuction(uint256)", "0x18489f50": "thingExist(bytes32[])", "0x5323c6cf": "calcCostsBuying(bytes,uint256,uint256[],uint8,uint256)", "0x9a777d5d": "buyCoins()", "0x36344022": "testAuthorizedTransfer()", "0x8b863095": "setContractorProposal(uint256,bytes)", "0xd10e99fe": "mint(int256,bytes32)", "0x12c82bcc": "sendRobust(address,uint256)", "0x0bf75567": "voteSuperQuorum(uint256,bool)", "0xf5f6ea26": "EthOne()", "0x14918f5e": "performInitialWithdrawal()", "0xdf32754b": "owned()", "0x1632070c": "setRewardDivisor(uint256)", "0xe50a3bb1": "oraclize_query(string,string[],uint256)", "0x9b619d3b": "_deleteAllPackedRevisionBlockNumbers(bytes32)", "0x3b751f7f": "claimThroneRP(string)", "0xd55ec697": "upgrade()", "0x43703b0e": "getEventData(bytes)", "0xd1bf9aba": "nextRune()", "0x76849376": "addNode(bytes32,address)", "0xfd958695": "isAlphaNumeric(bytes1)", "0x9fa5e5d5": "setARKowner(address)", "0x6e2cde85": "drawPot(string,string)", "0x4d5b080c": "scheduleTransaction(uint256,address,uint256)", "0xf1e4a540": "unsetCoordinator()", "0x364f4896": "emission(address,address,uint256,uint16,uint16)", "0x9183fd01": "getSeedPrice()", "0x9832ee65": "resultsWeightedByTokens()", "0xdd5244b4": "testTryProxyCallWithValue()", "0xec2ec781": "testFailGetUnsetToken()", "0x7db9743b": "Registry()", "0x1adf2d1a": "Offer(address,address,bytes,uint256,uint256,uint128,uint256)", "0x1ba326c4": "calcShare(uint256,uint256,uint256)", "0x3ced842b": "make_offer()", "0xea295ec2": "calcRevenue(address)", "0x3c894475": "scheduleTransaction(address,bytes,uint8,uint256[6],uint256)", "0x477bddaa": "setContractAddress(address)", "0xed180443": "getUint256(int256)", "0xee725d44": "toChannelID(string)", "0xa4898fd5": "deployContract(address)", "0x75ee85bd": "salsa20_8(uint256,uint256)", "0xbe7c29c1": "getNewDAOAddress(uint256)", "0xfe01f1ff": "TokenTester()", "0x6103d915": "Winners(uint256)", "0xa30b5c69": "AttributeModel()", "0x81064e2d": "getCreditorAmounts()", "0x23584a21": "initStats(string,address,uint256)", "0xe2894a8a": "OwnerAnnounce(string)", "0xa84c5330": "createNewRevision(bytes20,bytes)", "0xb742398b": "trade(address,uint256,bytes,address,uint256,bytes)", "0x3211bb90": "OwnerAddFunds()", "0xa0befa94": "getStake(uint256,uint256)", "0x6dc3edcf": "executeExecutable(uint256,uint256)", "0x17e875e3": "Transparancy()", "0x6939864b": "lotteryState()", "0x3b107682": "DualIndex()", "0x51b3d7b9": "_transferWithReference(address,uint256,string)", "0xdf7cec28": "cancelBid(bytes32)", "0x1e74a2d3": "getMinimumEndowment()", "0x39b333d9": "Play(uint8,uint8,uint8,uint8)", "0xcbd08c8c": "config(uint256,uint256,uint256,uint256)", "0xca6ad1e4": "setCustomGasPrice(uint256)", "0x510f44cb": "TestFactoryUser()", "0xcee6ee38": "aEthereumlotteryNet()", "0x11610c25": "bet()", "0xb73405a9": "roundMoneyDownNicely(uint256)", "0xb8851fea": "endDateStart()", "0xa4325485": "getCreatorBalance()", "0x2c181929": "getChainWork()", "0xffb4c857": "_confirmAndCheck(bytes32)", "0x9e66cd38": "free(uint64)", "0x44e43cb8": "depositRevenue()", "0xa553a597": "configure(uint256,uint256,uint8,address)", "0xc47f0027": "setName(string)", "0x565a2ecf": "classicTransfer(address)", "0x1da0fb1b": "updateSettings(uint256,uint256,uint256,uint256,uint256,bool)", "0xa5ebf389": "getMoneyTotals()", "0x7f445c24": "subRegistrar(string)", "0x9ad4f98e": "BlocksureInfo()", "0xd6b4ec12": "getDailyWithdrawalLimit()", "0xe0886f90": "at(uint256)", "0x5dc77e26": "andThen(string,address)", "0xe7d50e5c": "FarmShare()", "0x3f2965f0": "registerSeller(address)", "0x85b73d3c": "testCreateNewRevision()", "0x49437210": "getUpdatable(bytes32)", "0xe1a9109d": "setSeedPrice(uint256)", "0x95978868": "strConcat(string,string,string,string,string)", "0x511b1df9": "addr(string)", "0xc5b1d9aa": "newRound()", "0xecf6eb22": "setConfigAddress(bytes,address)", "0x9a9c9c53": "DepositToBankAccount()", "0x27ea6f2b": "setLimit(uint256)", "0xd2dc0869": "add(string,uint256,string,string,address)", "0xc86a90fe": "sendCoin(uint256,address)", "0x5dfc2e4a": "noop()", "0xd81e8423": "get(address,address)", "0x4cad42d3": "testWager()", "0xd120a284": "getBytesFromNumbers(uint8[3])", "0x991ffd4e": "scheduleCall(address,bytes,bytes,uint256,uint256,uint8,uint256)", "0x60c311fd": "doBurnFromContract(address,uint256)", "0xbb6a0853": "GreedPit()", "0xc27b2c2d": "collectEarnings()", "0x446294ad": "multiAccessGetOwners()", "0x5bec9e67": "infinite()", "0x47e7ef24": "deposit(address,uint256)", "0x8d216186": "roll(uint256,bytes32)", "0xdf300b46": "getThing(bytes32[])", "0x5a6c787e": "updateWithMPO()", "0x1f2dc5ef": "divisor()", "0x421aeda6": "Set_your_game_number(string)", "0xba1162d7": "getFmLength()", "0x853255cc": "sum()", "0x20768ee8": "getProposalID()", "0xf5c57382": "nameOf(address)", "0x4e417a98": "callData()", "0xc90d080a": "registerEvent(bytes)", "0x0b1e400a": "_transferFromToICAPWithReference(address,bytes32,uint256,string)", "0xe420264a": "g(uint256)", "0x8a00a82f": "withdrawRewardFor(address)", "0x06638e92": "GetNumbersFromHash(bytes32)", "0xf63da25b": "Emailer()", "0xc01706dd": "getContentByRank(address,uint256,uint256)", "0xe1108706": "rfind()", "0xffd10e07": "enterPool(address)", "0xc4254c7b": "CoreWallet()", "0x30e0789e": "_transfer(address,address,uint256)", "0x992ae976": "isSafePunctuation(bytes1)", "0x4afce471": "test_requires_depth(uint16)", "0xda7fc24f": "setBackend(address)", "0xeb7402f5": "multiAccessHasConfirmed(bytes32,address)", "0x6c1a5b8c": "TOKEN_TARGET()", "0xb3a2a999": "nextWithdrawal(bytes16)", "0x4a420138": "scheduleHeartbeat()", "0xb1233451": "setTerm(uint256,string)", "0x266710ca": "manualUpdateBalances_only_Dev()", "0x98866c1a": "personUpdateDOD(uint256,int256)", "0xaa6be303": "debtors(address)", "0xda5c0a7c": "testDisown()", "0x8757a2cd": "test_depth(uint256,uint256)", "0xe1bedf2a": "AlarmTester(address)", "0x5dcbac7a": "registerBytes(address,bytes)", "0xa587da29": "setPackage(bytes,uint8,uint8,uint8,bytes)", "0xc3da42b8": "c()", "0x4b8772c1": "buyUnit(uint256,uint256)", "0x67f12ecf": "validate(address,uint256,uint256[101][])", "0x0bd089ab": "MyAdvancedToken(uint256,string,uint8,string,address)", "0x32829a23": "OpenBankAccount()", "0xdea06188": "NumberOfBlockAlreadyMined()", "0x61e539da": "testFailWrongAccountTransfers()", "0x8a3e44d4": "assetMoveInformation(address,address)", "0x1327d3d8": "setValidator(address)", "0x207c64fb": "validate(address)", "0x80acaafb": "profitDistribution()", "0x90b98a11": "sendCoin(address,uint256)", "0x8b147245": "update(bytes32)", "0x920c94df": "BuyTicketForOther(address,uint8,uint8,uint8)", "0x6f698fb5": "setMinimumQuorum(uint256)", "0xac5e81a9": "historyPayout(address)", "0x70d084c0": "SingularDTVCrowdfunding()", "0x67ce940d": "getOverhead()", "0x7a791524": "setNextFeePercentage(uint8)", "0xd6d902c4": "claimThroneFor(bytes,address)", "0xc71e48d6": "setOutcome(bytes32,bytes32[])", "0xef41e06f": "testThrowSetEnforceRevisionsNotOwner()", "0x70be4ffa": "testErrorUnauthorizedSetPackage()", "0x6d12301c": "getBetValue(bytes32,uint8)", "0x4e69d560": "getStatus()", "0x55234ec0": "remaining()", "0x5cfd8c24": "ResetPonzi()", "0xe29fb547": "scheduleCall(bytes4,uint256,uint256,uint8,uint256)", "0xbd119967": "add_rating(uint256,uint256)", "0x966acb38": "testThrowTransferNotTransferable()", "0x5c665f89": "getFunds(address,bool)", "0xa59d6986": "recoverLostFunds()", "0x403abbc7": "updateFirstActiveGamble()", "0x0230a07c": "releaseDeed(bytes32)", "0x2d116186": "deityBalance()", "0x602acca1": "InchainICO(address[],uint256)", "0xd393c871": "register(string,address,uint256)", "0xe0fe075e": "payoutReady()", "0xf3e3c629": "testBalanceOfStartsAtZero()", "0x48cd4cb1": "startBlock()", "0x669459a7": "removeRegistryFromOwnerIndex(address)", "0x74d89c47": "testUpdateNameDb()", "0x182db370": "getWhatHappened()", "0xf363441f": "getCreatorDotBalance()", "0x6896fabf": "getAccountBalance()", "0xb29a0308": "logAnonymous(bytes,bytes,bytes,uint256)", "0x3023d0c4": "Ethstick()", "0x1077f06c": "makeClaim(uint256)", "0x8a519fb9": "BlockChainEnterprise()", "0xbffbe61c": "node(address)", "0xe20bbd8d": "RecoveryWithTenant()", "0x03bda14e": "raiseMaxNumBets(uint256)", "0xe1041d86": "__throw()", "0x3d79d1c8": "bal()", "0xbd3f0965": "AiraEtherFunds(string,string)", "0xc388cca6": "testBitAndFailIndexOOB()", "0x921f98bb": "resolveFailVote()", "0x4bbb216c": "_target(address)", "0x10922cc1": "testTransferCost()", "0xeceb2945": "checkProposalCode(uint256,address,uint256,bytes)", "0x3df4ddf4": "first()", "0x21a49ec2": "LCoin()", "0x6d1f00a6": "ThroneMaker(uint256)", "0xcaed4f9f": "DataService()", "0x5cff876b": "carrotsCaught()", "0x3a7fb796": "mintGreen(int256,address,uint256)", "0x370b6939": "AdminSetDrawer(address)", "0x2d9a37d3": "getMaxPayout()", "0x739f888c": "setNewEstimate(int256,int256)", "0x04dd69fa": "getGenerationIdForCall(address)", "0xb764e273": "failSend()", "0xd4dfadbf": "getMarket(address)", "0xa0e67e2b": "getOwners()", "0x2d592a34": "sellKissBTC(uint256)", "0x8a341c83": "testErrorRootAuthorityChangeUnownedPackage()", "0xfcc6b5d5": "fillTheirOrder(address)", "0x61649472": "getPoolFreezePeriod()", "0x3dd297da": "safeMultiply(uint256,uint256)", "0x5d3278f0": "LooneyFifty()", "0x7399646a": "theRun()", "0x3e450fff": "adminDeleteAccount()", "0x93cc9162": "taskRejected(uint256,uint256)", "0xbc5d0f65": "beginExecution()", "0x1934d55a": "isPermanentlyApproved(address,address)", "0xf1cff4b5": "testBitsNotSetSuccess()", "0xf240f7c3": "dispute()", "0xf6d5959b": "getActionStatus(uint256)", "0x0f23cbaa": "recycle()", "0x74f8d96e": "getRevisionBlockNumber(bytes20,uint256)", "0x0bad342a": "EscrowContract(address,address,address,address,uint256,uint256,uint256,uint256)", "0xa6afd5fd": "getBets()", "0x84ad6ff3": "ReversibleDemo()", "0x62b3b833": "createCoupon(string)", "0x523ccfa8": "isKnownCall(address)", "0xacc8cb18": "pushTerm(string)", "0xa753d6f2": "CreateProposal(string,string,string,string,string,string,uint32,uint32)", "0xc5699d68": "_compare(int256,bytes,int256)", "0xe2faf044": "createDAO(address,uint256,uint256,uint256)", "0xa15afb48": "Replicator()", "0x352d2790": "UUID4()", "0x7dee2cad": "CancelMyInvestment()", "0xbeabacc8": "transfer(address,address,uint256)", "0x674f220f": "previousOwner()", "0x4b3b6168": "SetNewBigContract(address)", "0x54ba7daa": "enter(bytes,bytes)", "0x06fdde03": "name()", "0x5944427b": "getRequestResult(uint256)", "0x983ef725": "getDifficulty(uint256)", "0x9287c877": "getNavLength()", "0xa987d654": "restoreItem(uint256)", "0x05433a26": "GetNumbersFromHash(bytes)", "0xd04bfc9c": "buyer_pay()", "0x4a9b3f95": "personUpdateName(uint256,string)", "0xe6b972f5": "userName(address)", "0x88782386": "UnicornMilk()", "0x74580e2f": "changeCreator(address)", "0x1785f53c": "removeAdmin(address)", "0xad544dcb": "testSetNotUpdatable()", "0xafd09bab": "quadrupler()", "0x77bc222c": "_eraseSingleNode(bytes32)", "0x09957e69": "newSale(bytes,uint256,uint256)", "0xa21931ea": "CreateProposal(string,string,string,uint32,string,string,string,uint32,uint32)", "0xbb6b4619": "SendETC(address)", "0x3aa94b1d": "getCoinStats(uint256)", "0x9e2262f5": "testCreateCostData()", "0x2bf4e53d": "getCurrentShareholders()", "0x6111dd02": "calcCostsSelling(uint256,uint8,uint8,uint256)", "0x6b9b1006": "TransactionRecorder()", "0x83b23b40": "cEthereumlotteryNet()", "0x770c6cbb": "WithDrawPreForkChildDAO()", "0x67080f6e": "testThrowsSetEnforceRevisionsNotOwner()", "0xa10bee85": "_transferFromWithReference(address,address,uint256,string)", "0x49cc954b": "twoYearsPassed()", "0x88c3ba85": "ParallelGambling()", "0x03985426": "getMode(bytes32)", "0xad8ed335": "__proxy(address)", "0x306387a4": "dealStatus(uint256)", "0x0343dfa0": "checkInvariants()", "0x23df9df5": "_refund(uint256)", "0x837e7cc6": "rollDice()", "0x98b1e06a": "deposit(bytes)", "0xa0440426": "purchaseProduct(uint256,uint256)", "0x4cb71b9b": "getAllReleaseHashes()", "0x3a7d280c": "login(string)", "0xb9a904f9": "testUnauthorizedSetBetaPackage()", "0xe94a4db1": "isSuitableGen(uint256,uint256)", "0x60f8af90": "refundRound()", "0x43bf718e": "getHashOfTheProposalDocument()", "0x4a30f976": "censorship(uint256,bool,bool)", "0x47e46806": "toString()", "0x8d59cc02": "register(address,string,string)", "0xb3fb14ad": "getGameResult()", "0x4a23dc52": "FileStore()", "0x8da5cb5b": "owner()", "0x3cc8daf7": "setNameOwner(bytes,address)", "0x14cbdb54": "EspCoin()", "0xc47cf5de": "getAddress(bytes)", "0x71e11354": "updateRegistration(string,string)", "0x8b2e6dcf": "publish(bytes32)", "0x12d00c2e": "soloWithdraw(uint256)", "0xd68199dc": "gameStats()", "0xf0f967e8": "canCall(address,address,bytes)", "0xe0c7c117": "Randao()", "0xddeae033": "claimFor(address)", "0xcec7260b": "move_monster(uint16,uint16)", "0xe51ace16": "record(string)", "0x4f76cb02": "testGetBitFailIndexOOB()", "0x942385eb": "getPayroll()", "0x46d667db": "setBytes32(bytes)", "0x35a063b4": "abort()", "0xb1d51d31": "pay(uint64,address)", "0x7140bdf3": "get_all_best_offers()", "0x0380e2f3": "getHashOfTheSignedDocument()", "0x2feda2fa": "POI()", "0x3c0870ae": "challenge(uint256,uint256,uint256,bool)", "0x27a5c7c6": "voteDecline(uint256)", "0xafd8c8c4": "GasProxy(address,address)", "0x8de93222": "purchase(address,uint256)", "0x087e055a": "getConfigBool(bytes)", "0xbaccc92b": "RegulatorIfc(address)", "0x8c546f81": "GNT()", "0x57cb2fc4": "getInt8()", "0xe3083fb5": "removeFromContribution(uint256)", "0x1a092541": "getDescription()", "0x7486a8e3": "get_publisher(bytes32)", "0x089e0ad0": "buildDSMap()", "0x29161820": "Base(uint256)", "0xe2f8feb2": "internal_tester(int256)", "0x1d2dbb22": "CancelMyInvest()", "0x726ab4ef": "getParentHash(bytes)", "0x83d8a90f": "theDonkeyKing()", "0x9babdad6": "removeShareholder(address)", "0xdeb931a2": "getOwner(bytes32)", "0x90cb04e1": "buy(string,uint256,uint16)", "0x2ff92323": "oraclize_query(uint256,string,string[4])", "0xf91a792e": "decryptHand(string,uint256,uint256,uint256)", "0xebf6e91d": "hit(uint256)", "0xb085b9a5": "Example()", "0x07b2779f": "BasicRegulator(address,uint256,uint256)", "0xe10e5dce": "_build(bytes)", "0x3e239e1a": "getHour(uint256)", "0xacc5a0dc": "GetPrize()", "0xa79deb4f": "acceptTradeDeal()", "0xd7c26adb": "oraclize_setProof(bytes1)", "0xc6a17d2b": "pow10(uint256,uint8)", "0xa87d942c": "getCount()", "0xe706918c": "testToggleBitSuccess()", "0xc1812b15": "reorganizeOwners()", "0x7c7c7695": "getAccountID(address)", "0x1a26ed1c": "validateReservedWindowSize(uint256,uint256)", "0x87393bc6": "verifyFirstHalf(uint256[4],uint256[4])", "0x2e52d606": "n()", "0x2037fcbf": "withdrawInvestment(uint256)", "0x77228659": "query2(uint256,string,string,string)", "0xf67a1d37": "BlockChainChallenge()", "0xc67146a5": "check_bet(uint256,address,uint8)", "0xc89f2ce4": "funds()", "0x58e29e17": "initiateProof()", "0xf0e10c0d": "play(address,uint256)", "0x480b70bd": "scheduleCall(address,bytes4,uint256,uint256)", "0x5294157f": "sendWithAllOurGasExceptExt(address,uint256,uint256)", "0xad447a19": "getBalanceDB()", "0x41095b60": "voteForUltimateOutcome(bytes,uint16)", "0x5521d17b": "betOnColor(bool)", "0xc8f8d75d": "Config(uint8,address)", "0x10ae4ce2": "setReleaseValidator(address)", "0x9fd4f7d1": "replaceWizard(address)", "0x77c78df9": "getCurrentLevel()", "0x1dda5c7d": "testFailSubBalanceBelowZero()", "0x21e5383a": "addBalance(address,uint256)", "0xb0414a2d": "setMinimumGasLimit(uint256)", "0x919840ad": "check()", "0xf651bf44": "move_to(uint16)", "0x0b97bc86": "startDate()", "0x29f1bff4": "withdrawFromChildDAO(uint256)", "0x7a02dc06": "getInfo(bytes32)", "0xe5bf93b9": "balanceEther(uint256)", "0x288c6ed2": "getSeedCost(uint256)", "0x4db3da83": "scheduleCall(bytes4)", "0x270cfee1": "getTokenAccount()", "0xb5d03751": "YoutubeViews()", "0x27e056a5": "addMinter(int256,address)", "0xfa7d68f1": "getAccountInfo(uint256,uint256)", "0x09fc8f6d": "isTokenUpgraded(bytes32)", "0xa5b9e922": "getContentTimetamp(uint256)", "0x1f8947c1": "extractUint(int256,bytes,uint256,uint256)", "0xbcc941b6": "totalWinners()", "0x1db71ffb": "doLoops(uint256)", "0x35ae41c9": "godAutomaticCollectFee()", "0x42cf0e72": "searchByOwner(address)", "0x69f18967": "testSetBitFailIndexOOB()", "0x57b07cd9": "getReleaseHash(uint256)", "0x2baf4f22": "_safeFalse()", "0x3133f2a7": "outstandingBalance()", "0x773c84ee": "exec(address,bytes,uint256,uint256)", "0x9070b18d": "_getAllRevisionBlockNumbers(bytes32)", "0x476e04c7": "NewMessage(string)", "0x3b91ceef": "setMax(uint256,uint256)", "0xe6cb9013": "safeAdd(uint256,uint256)", "0xc2038560": "setOutcome(bytes,bytes)", "0xbed34bba": "compareStrings(string,string)", "0xba8661a2": "TimestampScheduler(address)", "0x4c4aea87": "getReleaseData(bytes32)", "0x1177892f": "getBalanceByAdress(address)", "0x126a710e": "dnsrr(bytes32)", "0x60913244": "botOnSale(uint256,uint256)", "0x5731f357": "oraclize_query(uint256,string,string,string)", "0x3e58c58c": "send(address)", "0x2187a833": "setGreenToken()", "0x5f09952e": "voteAllowTransactions(bool)", "0xf2b26d8f": "nextEtherForSale()", "0x1f201e39": "etherandomExecWithGasLimit(bytes32,bytes32,uint256,uint256)", "0x43d726d6": "close()", "0x6cdf4c90": "ownerSetMinBet(uint256)", "0xe6e8c692": "computeResponseFirstHalf(uint256,uint16)", "0xd1d80fdf": "setAddr(address)", "0x6da1833c": "getInstitutionByName(string)", "0x7682e6ff": "getTrustSetting(address)", "0x8f6f988c": "setUltimateOutcome(bytes)", "0xe299beb3": "SimpleIndex()", "0xa2bb5d48": "get_username(address)", "0x780900dc": "create(uint256)", "0x78710d37": "seven()", "0x2b20e397": "registrar()", "0x4094ef5e": "addDataRequest(string)", "0xc630f92b": "canEnterPool()", "0xdd114c22": "publish(address,uint256,address,uint256)", "0x57006864": "checkBetParity(uint8)", "0x45788ce2": "prev(address)", "0xee8ff562": "setMaxProfit()", "0xdc63a62c": "getFileListHead()", "0x9447fd0a": "until()", "0xb303dcbd": "Owned()", "0x0ecaea73": "create(address,uint256)", "0x20339891": "addGridMember(address)", "0xc8c01a55": "request(address,uint256)", "0x76f10ad0": "getSnapshot(uint256)", "0xc06c4474": "get_burned(bytes32)", "0x67cb61b6": "getChoice()", "0xca708230": "funnel()", "0x08b7fa31": "PriceFeed()", "0xac6bc853": "startSpin()", "0xf4bbfd6a": "scheduleCall(bytes,bytes)", "0xab73e316": "next(address)", "0xba0179b5": "confirm(uint256)", "0x1e62be25": "Bytes32Passer()", "0xb950556a": "setThingValid(bytes32[],bool)", "0xb61c0503": "fireEventLog1()", "0x79a85e6c": "getProductInfo(uint256)", "0x959ac484": "push(uint256)", "0x78e80b39": "UserGetPrize()", "0xff74927b": "strConcat(string,string)", "0xd207e757": "ownerSetOraclizeSafeGas(uint32)", "0x5819dde2": "getNumbersFromBytes(bytes3)", "0xf34ed4e6": "RanDAOPlus(address)", "0x3943807b": "insert(bytes,bytes,int256)", "0x38cc4831": "getAddress()", "0x12a7b914": "getBool()", "0xa4fd6f56": "isEnded()", "0x6c86888b": "testTrade(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32,uint256,address)", "0x0bebd0f9": "addAddressToGeneration(address,uint256)", "0x003074ff": "getFrontend()", "0x2776a859": "computeResponseSecondHalf(uint16)", "0xadfe6b80": "InvestAdd()", "0x6981b5f4": "getLength(string)", "0x4a994eef": "setDelegate(address,bool)", "0xbac1e9f6": "getChannelSize(address,uint256)", "0xbdfdb519": "accept(string,uint256,uint16)", "0x68f5aa0f": "setShareholderDB(address)", "0xafb95eed": "logApproval(address,address,bytes32)", "0xae152cf4": "oraclize_query(string,string,uint256)", "0xe3b26a8c": "SocialNetwork()", "0x54204ad4": "triple()", "0xbc8f3bcb": "ZeroDollarHomePage()", "0xf0350c04": "transfer_ownership(address)", "0xfde9ba41": "transfer(bytes,address,uint256)", "0xfa2acd87": "G(uint64[16],uint256,uint256,uint256,uint256,uint64,uint64)", "0x3a76abff": "_eraseNode(uint256,bytes32[],bytes32)", "0x0acf473b": "AdminCloseContract()", "0x299e7318": "resolveVoting()", "0xb414d4b6": "frozenAccount(address)", "0x8d375da2": "testMakeItFail()", "0x6e8dad74": "retrieveAccountBalance(bytes,bytes)", "0xd3aa831f": "testOwnedTryAuth()", "0x13220305": "doTransferOther(address,address,address,uint256)", "0xb0c7f709": "kingAutomaticCollectFee()", "0x2dabbeed": "reclaim(uint256)", "0x5c19a95c": "delegate(address)", "0xbc2a4dd6": "doBalanceOf(address)", "0x8e7ea5b2": "getWinner()", "0xdb37e42f": "multisetProofType(uint256[],address[])", "0xa6b197aa": "Order(address,uint256)", "0x8cf4dbfb": "collectBalance()", "0xd0821b0e": "bet(uint8)", "0xa02b161e": "unregister(uint256)", "0x09dd0e81": "getBlockchainHead()", "0x8a0807b7": "indexOf(string,string)", "0xe3914699": "dEthereumlotteryNetWinners(address)", "0x44d75fa9": "updateMinorTree(bytes32)", "0x3c21db0a": "theGames(uint256)", "0xf0e959f9": "TokenSales(address)", "0x696bda86": "submitProposal(uint256,bytes)", "0x3b343a13": "getNodeAddress(bytes)", "0x2812f8b8": "FutureCall(address,uint256,uint16,address,bytes4,bytes,uint256,uint256,uint256)", "0xbf32bf97": "FailGuyTax()", "0x89ced196": "setNotUpdatable(bytes32)", "0xb94e962a": "allocateTickets(uint256)", "0x7a479160": "getRequestArgs(uint256)", "0x5a825cbb": "getPayment(uint256,uint256)", "0x4ca8b0d0": "registerExistingThrone(bytes,address,uint256,uint256)", "0x82afd23b": "isActive(uint256)", "0x6ebf10fe": "storeHeader(bytes,address)", "0x1437f9a3": "Set_your_game_number(uint16)", "0xd98d011d": "getCandidateKey(bytes,bytes,bytes,bytes)", "0x8b676ae8": "scheduleCall(address,bytes4,uint256,uint256,uint8,uint256,uint256)", "0x90fd53ec": "farmTile(uint8,uint8,int8)", "0x46e44f63": "getCheckRecordTS(bytes)", "0x1de38038": "makercoin(uint256)", "0xc038a38e": "totals()", "0xfa80918b": "computeNodeId(bytes,bytes)", "0xc76a4bfb": "relayReceiveApproval(address,address,uint256,bytes)", "0x2406cedb": "setPackageOwner(bytes32,address)", "0xb7297cf3": "gameSettings()", "0xe94acf0e": "TinyRouter(address)", "0x4a2b0c38": "DividendProfit()", "0x0e3f732a": "TheGame()", "0xd62457f6": "callValue()", "0x4961b40c": "getReleaseValidator()", "0x540cafe0": "storeHeaderWithFee(bytes,int256,address)", "0x7ff729fc": "fillUpProject(uint256,uint256)", "0x253459e3": "feesSeperateFromBalanceApproximately()", "0x930a80b4": "testAuthorizedSetPackage()", "0xb3cb8885": "nextUnderdogPayout()", "0x62c7855b": "getConfigBytes(bytes32)", "0x4f28af6a": "handleBet(uint256)", "0x103f9251": "transferFrom(address,address)", "0x9b19251a": "whitelist(address)", "0x9928811b": "testBroken()", "0xb33a8a11": "setTokenReference(address)", "0x27f06fff": "requestFillUp(uint256)", "0x2f570a23": "test(bytes)", "0x96ef7aa0": "cash_transfered(string)", "0x3983d5c4": "calcBaseFee(uint256)", "0xec0f1025": "testBitsOrSuccess()", "0xd35f4a99": "mint(int256,address,uint256)", "0x09dfdc71": "currentPyramidBalanceApproximately()", "0xac4e73f9": "proposeReverse(string,address)", "0xac4bd53a": "currentLeader()", "0x5a2ee019": "m()", "0xeba36dbd": "setAddr(uint256,address)", "0x0358d965": "addPayout(uint256)", "0xd7206124": "setInvestorLock(bool)", "0xe916d0f0": "doBalance(address)", "0x67c2a360": "authorizeUser(address)", "0x828d671c": "dyn_sig()", "0xaf6fe8e2": "testGetToken()", "0x283a4576": "Tomeka()", "0x8ac0ca36": "buyViaJohan()", "0xcc872b66": "issue(uint256)", "0xd826f88f": "reset()", "0x2aa3177a": "self_store()", "0x53b7b2e9": "cEthereumlotteryNet(bytes)", "0xce88b145": "getAccount(uint256)", "0x1fa03a2b": "isApprovedFor(address,address)", "0xe42d5be0": "getPaymentOf(address)", "0xb722a9ef": "getPreviousShareholder(address)", "0xfadf87b1": "testGetBitsSuccess()", "0xd26c8a8a": "coinBalance()", "0x30ccebb5": "getStatus(address)", "0x47799da8": "last()", "0x4a5db3b5": "authorizeAddress(address)", "0x22686250": "index(int256,uint256)", "0x07ef99a0": "demintTokens(int256,address,uint8)", "0xea2d4cf8": "__DeployerFunctions(address,address,uint256)", "0x092a2e37": "multiAccessAddOwnerD(address,address)", "0x671fa0a0": "Inscription(string)", "0xa10edc55": "GeneralPurposeProfitSplitter()", "0xd9c67404": "getMerkleRoot(bytes)", "0xdc419fd8": "cancelOrder(bool,uint256)", "0xc9734ebd": "WatchLastPayout()", "0xc7d6faf1": "easyPropose(address,uint256)", "0xfe63300a": "registerExternalBill(uint256,address,address,uint256,uint256,uint256)", "0xd3437fe0": "assertFact(uint256,bytes)", "0x5fb3e119": "Auction()", "0x665beae7": "ExecutableBase(bytes)", "0xc8bb73ef": "testGetBitsFailIndexOOB()", "0xc1d4f708": "getMwLength()", "0x22b0f6ee": "getStatusOfPayout(uint256)", "0x21520c5f": "calculatePayout(uint8,bool,uint256)", "0x66e98c31": "createCoin(string,uint256,uint256,string,string,address)", "0x7b352962": "isFinished()", "0x48d9614d": "GetFee()", "0xfe0d94c1": "execute(uint256)", "0xe4547f98": "documentExists(bytes)", "0x4e10c3ee": "transferWithoutReward(address,uint256)", "0x58ae8bcf": "voteInMasterKey(address)", "0x1dcb304b": "fipsGenerate()", "0xb595181f": "ShapeshiftBot()", "0xe02426c1": "getSignatureHash(bytes4,uint256)", "0x67546967": "EthBtcEscrow()", "0x85b31d7b": "myInfo()", "0xaa677354": "register(address,address)", "0x1d2e2cc4": "ENS()", "0x1097e579": "Enter()", "0x13a396d8": "getRequiredDeposit(bytes)", "0x6df3edef": "getSavedBytes()", "0x09b30ed5": "afterExecute(address)", "0x718e6302": "play(string)", "0x8e46fbb2": "testBitsXorFailIndexOOB()", "0x0d87a7c0": "WLBDrawsDB()", "0xbbe42771": "closeDeed(uint256)", "0xedfbf7b6": "setVotingDeadline(uint256)", "0x299e6b07": "Wallet(address)", "0x5cbc85d0": "returnBounty(uint256)", "0xe5225381": "collect()", "0x94f60a63": "getKudosLeft(address)", "0xd6960697": "confirmPurchase()", "0x4a1f0bf6": "inheritToNextGeneration(address)", "0x244ded7a": "ChangeOwnership(address)", "0x39b35753": "authCancel(address)", "0x75cb2672": "configure(address)", "0x938ae4cc": "testThrowDisownNotTransferable()", "0x04fc11d5": "getActual()", "0xacab021c": "getTOS(address)", "0x812cddf2": "getSavedString()", "0x8ae475a9": "notorize(string)", "0xb1d05422": "SendEmail(string,string)", "0x0fffbb54": "changeRankingSize(uint256)", "0xb6ed9f15": "PFOffer(address,address,bytes,uint256,uint256,uint128)", "0xda333ca6": "payOut(uint256)", "0x652f1f16": "addSignature(string)", "0x983b2d56": "addMinter(address)", "0x5e1936d4": "testThrowSetNotTransferableNotOwner()", "0x4ac1ad78": "getWeekday(uint256)", "0x6ba0b4f2": "isKnownSelector(bytes4)", "0x7c4c27c8": "isThisPuritanicalVersion()", "0x7ae2b5c7": "min(uint256,uint256)", "0x63bd1d4a": "payout()", "0x3fd94686": "changeEligibleDonkeys(uint256)", "0x7fe0518a": "asyncSend(address,uint256)", "0x5a8dd79f": "getDesignatedCaller(address,uint256)", "0x2635f4de": "registerLibrary(bytes,address)", "0x1335ff36": "createEventAndMarketMaker(uint256,uint256,uint8,uint32,address,uint256,uint8,uint16,uint256)", "0x181be00d": "getValue(uint8)", "0x9c1500f0": "registerMany(address,uint256,int256,uint256,bytes,address,bytes)", "0x16f9ce49": "_slotCommitNew(address)", "0x8ca4eef6": "getBuild(bytes32)", "0x8ee21b8e": "get_default_keys()", "0xa66f7ad6": "signRelease(uint256)", "0x414053be": "best_adjustment_for(bool,uint128)", "0x83a51ad0": "oraclize_setConfig(bytes32)", "0x262c0b72": "getPayoutFreezePeriod()", "0x499af77c": "current_spin_number()", "0x4209fff1": "isUser(address)", "0x6e1b6bcc": "checkMyBet(address)", "0xb46300ec": "send()", "0x6b1cb549": "orderMatch(uint256,uint256,uint256,int256,uint256,uint256,address,uint8,bytes32,bytes32,int256)", "0x58d9fa04": "addUser(uint256,address)", "0x24c93343": "error(string)", "0xa95d017d": "getRevisionBlockNumber(bytes32,uint256)", "0x46af23f5": "InstantLottery(address,address,bool,address)", "0x29ef56b1": "getAskOrderBookStats()", "0xe97db66e": "setJackpot()", "0x4b59e880": "puzzle(address,bytes32,bytes32)", "0xc7f86c37": "withdrawFundsRP()", "0x57d4021b": "nextPayoutWhenPyramidBalanceTotalsApproximately()", "0xf1c760ae": "fixBalanceInternal(address)", "0x0908178f": "NoFeePonzi()", "0x22ebb3ac": "DieselPricePeg()", "0xb39a64cd": "getNumCalled()", "0x1c02708d": "killContract()", "0x65228934": "setOperationsCallGas(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x3397ca17": "numBalanceRecords(address)", "0xe436bdf3": "Draws(uint256)", "0xaf55bba0": "removeRegistryFromTagsIndex(address)", "0x11103599": "Token_Offer(address,address,uint16)", "0xb3a2a6c0": "setOfficialWebsite(string)", "0xb06eb03f": "DSEasyMultisig(uint256,uint256,uint256)", "0x775c300c": "deploy()", "0xb1c6517a": "LookAtNumberOfPlayers()", "0xc19d93fb": "state()", "0xac9873c7": "CanaryV7()", "0x750cae6a": "enableBetting_only_Dev()", "0xdf3c8620": "num_challenges()", "0xbb00fc55": "bookEarnings()", "0x0bd2ae1c": "ERW()", "0x580bdf3c": "disableBetting_only_Dev()", "0x5c3f9765": "endDateClose()", "0xc4128b6d": "upgradeCount()", "0x140b4465": "executeSpendingRequests()", "0xfaa1a8ff": "getOwnedBot(address,uint256)", "0x40e58ee5": "cancel(uint256)", "0xf4ea95b9": "validateReleaseVersion(uint32[3])", "0x2ba0b09f": "AddNewCategory(bytes4,uint8,uint8,address)", "0x55db4092": "setTOS(address,bool)", "0xf9cc0605": "getAvailable()", "0x3f2f46b4": "revealRock(string)", "0x2af7ceff": "testPrice(uint256)", "0xcaaf2dd7": "getInitialAnswerResult(uint256)", "0x6f36ce79": "insert_deal(address,address,uint64,uint128,uint32)", "0xa18c751e": "set(bytes,bytes)", "0x4d536fe3": "doit()", "0x3197cbb6": "endTime()", "0xb83069c5": "getStemPrice()", "0x3f15457f": "ens()", "0x81ebdeea": "testThrowCreateWithNonceRetracted()", "0x249b4d0b": "removeTrustedIssuer(address,bytes)", "0xe7b48f74": "get(int256,address)", "0x089d5c4a": "repr()", "0x3a9e7433": "scheduleCall(bytes4,uint256,uint256,uint8)", "0x504f1671": "getSize(address)", "0xf3a44fe1": "withdrawForWorkshop()", "0x7a837213": "setAllowedAccount(address)", "0x4551b1d7": "ProxyPayment(address,address)", "0x5dac1601": "SimpleStablecoin()", "0x87914c6f": "prolongateContract()", "0xe3ceb06d": "YesNo(bytes32,address,string,address,uint256)", "0x42a745cb": "testBitEqualSuccess()", "0xaa8dea8c": "fipsAddToLedger(bytes20,address,bytes)", "0x986dcd4d": "setCycleLimit(uint256)", "0x1e44c112": "find_strike(uint64,uint32,uint32)", "0x1ecfe64d": "_jSub(uint256,uint256,uint256,uint256)", "0x4ae85627": "grindUnicorns(uint256)", "0x200ebe34": "addTokensToGive(address)", "0x7b632c41": "TimestampScheduler(address,address)", "0x979b6f6f": "RoundInfo()", "0xb3ea3924": "PointlessCoin(int256,uint256,string,uint8,string,address)", "0x1d834a1b": "insert(uint256,uint256)", "0x931df75f": "validateProposedThroneName(bytes)", "0x6189be15": "columnround(uint256,uint256)", "0xdf5dd1a5": "addOracle(address)", "0x22d8cf5b": "CheckUserVote(uint8,uint8)", "0xdd62ed3e": "allowance(address,address)", "0xc0eb2325": "scheduleTransaction(address,bytes,uint256)", "0x038461ea": "getCertifiedStudentsCount()", "0x9ee035c9": "lookupCanonicalFormat(bytes)", "0x0ab58ead": "SingularDTVFund()", "0x550ed1f0": "getMaxBetAmount()", "0x6e63015c": "getCertifiersCount()", "0xbbd4f854": "buyShares(bytes32,uint8,uint256,uint256)", "0x306b031d": "getGenerationEndAt(uint256)", "0x1bd9c46e": "setImporter()", "0xc80c28a2": "getNumberOfParticipants()", "0xcb553ac9": "sweepWizardCommission(uint256)", "0x6389654e": "changeDailyWithdrawalLimit(uint256)", "0xc0a963c9": "notifyWinner(address,uint256)", "0x3c335b0e": "getRetractable(bytes20)", "0x017972af": "getNumbersFromHash(bytes32)", "0x07da68f5": "stop()", "0x1e8c72b4": "incrUserAvailBal(address,uint256,bool)", "0x0df71602": "setWinner(uint256)", "0x85e5bb3a": "Security_AddPasswordSha3HashToBankAccount(bytes32)", "0x2ade6c36": "getNodeAddress(bytes32)", "0xb33926cb": "owner_withdraw(uint256)", "0x57764094": "getRate(uint256)", "0x13827950": "getShareholderDB()", "0x26826bf8": "setImage(bytes)", "0x76d438b0": "sendReward(uint256,uint256)", "0x51d6e547": "getNonce(bytes)", "0x4f24186a": "newProposal(string)", "0x1a695230": "transfer(address)", "0xe820a32f": "vetoPayout(uint256,uint256)", "0xfb3a1fb2": "getReleaseDb()", "0x6bae05cf": "preRegister(address)", "0xae2df7b3": "setImporterBank()", "0x56d73ad1": "getCertifierDb()", "0x8396392d": "add(string,string,string,address)", "0xeb08b304": "changeMeatProvider(address)", "0xb60e72cc": "log(string,uint256)", "0x76999896": "KingOfTheEtherThrone()", "0xea851885": "buyStake(bool)", "0xa5982885": "assertFalse(bool)", "0xfbf1f78a": "unapprove(address)", "0xc2ba5b40": "getPackageData(string)", "0x83d51a38": "concatString(string)", "0x68af4971": "registerListening()", "0x14ba5c09": "getDay()", "0xe4849b32": "sell(uint256)", "0x44dd4b3b": "lookupGeneration(uint256)", "0x3fda1281": "get_keys()", "0x09241200": "wasSuccessful()", "0xbc9147a4": "Foundation()", "0xe33734fd": "changeProposalDeposit(uint256)", "0x2cce4abe": "_finishNoCallback()", "0x45e965cd": "strConcat(string,string,string,string)", "0xd4245e5b": "transferringETH(address)", "0x10082bff": "getActorBillXdetail(address,uint256,bool)", "0xc831391d": "getPoolOverlapSize()", "0x15e33901": "digest(bytes,uint256)", "0x5f0edfb8": "create(bytes,bytes32,bytes1)", "0xadd4c784": "getResult(bytes32)", "0x3233c686": "claimerDeposit()", "0x187a62d5": "voteEmergencyWithdrawal(bool)", "0x5404bbf7": "getEntropy()", "0x3f5b7675": "periodTwo()", "0x7ec0f30d": "ack(string)", "0xd30a512e": "betOnColumnOrDozen(bool,bool,bool)", "0x9fcbc738": "setIntermediate(address)", "0x539e2bfb": "secondChainedCallback(uint256)", "0xe724529c": "freezeAccount(address,bool)", "0x5aa97eeb": "getMarkets(bytes32[],address)", "0xc51be90f": "query_withGasLimit(uint256,string,string,uint256)", "0x531b97d7": "oneCentOfWei()", "0xeace4827": "player_make_bet(uint8)", "0x9c851ebc": "new_entry()", "0x98c9cdf4": "getMinimumCallGas()", "0xd8f012c6": "StatelessFactory(string,string,string)", "0xceeafd9d": "withdrawFundsAdvancedRP(address,uint256,uint256)", "0xd6d7d525": "get(bytes)", "0xbf55486b": "Tanya()", "0xd35ada32": "addParticipant(address,address)", "0xa8239d0b": "getPrice(string,address)", "0x12514bba": "transfer(uint256)", "0xb00140aa": "getHash(bytes)", "0x36f66528": "EtherDelta(address,uint256,uint256)", "0x279e0912": "getDownloadPrice()", "0x8173b813": "setNumCities(uint256,uint256)", "0xb98fdc36": "IconomiToken(uint256,string,uint8,string,uint256)", "0x3c7a3aff": "commit()", "0xcac77df7": "__transferFromToICAPWithReference(address,bytes32,uint256,string)", "0xfbeaebc6": "murder()", "0x2fa7cbfb": "getExecCost(uint256)", "0xe44d3084": "testFailure()", "0xede8acdb": "startAuction(bytes32)", "0xd5544f94": "getFundsAndAvailable(address)", "0x3824d8ee": "buy100DaoFor1Eth()", "0xbe3945e4": "getFee(address,address,uint256)", "0x1917ab5c": "activate(string)", "0x23509e69": "donkeysEligibleForFees()", "0xf460590b": "updateSigner(address,bool)", "0xc55c1cb6": "queryN_withGasLimit(uint256,string,bytes,uint256)", "0xd96d7ea2": "PRE_EXECUTION_GAS()", "0x1f7b6d32": "length()", "0xacf8bf2a": "channelCount()", "0x18968a03": "finalize(uint256,address,address)", "0x5674a3ed": "runLottery()", "0xe3280126": "addOrder(string,bool)", "0x32afa2f9": "claimEtherOwner(uint256)", "0x355474d2": "commitReading(address)", "0x00601d6c": "board(uint256,uint8,uint8)", "0x2667f407": "__proxy(address,bytes)", "0x79716e43": "confirmTransaction(bytes32)", "0x272cda88": "EternalDB()", "0x76ca0c77": "scheduleCall(address,bytes,uint256,bytes,uint256)", "0x338b5dea": "depositToken(address,uint256)", "0xfa93f883": "getMinute(uint256)", "0xafc4a982": "PathCost(uint16,uint32)", "0xf7d97577": "setPrice(uint256,uint256)", "0x842bc37b": "GetSmallCotractIndex(address)", "0x75f40f40": "underdogPayoutFund()", "0x23a1c271": "setPongval(int8)", "0x02571be3": "owner(bytes32)", "0xf79b22e0": "betOnATeam(uint256)", "0x10cf5d47": "awaitingPayout()", "0x1b00fe51": "testHypothesis()", "0xd449ce7c": "Administered()", "0x455259cb": "getGasPrice()", "0x975057e7": "store()", "0xdfdb5f17": "doBurn(address,uint256)", "0xaa497b9d": "scheduleCall(address,uint256,bytes,uint256,uint256,uint8)", "0xcabfb934": "replace(address)", "0x1f1f5e76": "addValueToContribution(uint256)", "0xa0eda9f2": "_transferFee(address,uint256,string)", "0xa8d95fb2": "claim(address,string)", "0x03ee8f08": "getCoeff(uint16)", "0x9872a20a": "registerUInt(address,uint256)", "0xb20d30a9": "setDailyLimit(uint256)", "0xe116b17e": "getKudosLeftForProject(address,address)", "0xf7c9f74a": "insert_contribution(address,uint256)", "0xc7a1865b": "play(bytes32)", "0x356594ab": "EtherTransfer()", "0xe22b0c46": "verify(uint256,uint256,uint8,bytes,bytes)", "0x2fea7b81": "getIdentity(address)", "0x6fa8de90": "changeMeatParameters(uint256,uint256)", "0x37c390e3": "allow_move(uint16)", "0xd22c391a": "validateProposedThroneRules(uint256,uint256,uint256,uint256,uint256)", "0xf6d339e4": "setAddress(bytes32,string,address)", "0xd7cc8362": "isLatestMajorTree(bytes32,bytes32)", "0xdd012a15": "setIt(uint256)", "0x254c91b3": "testBitNotSetSuccess()", "0x47e40553": "nextRound()", "0xa6b206bf": "doSomething(uint256)", "0xac996e7e": "resolvePledging()", "0x71e2d919": "lol()", "0x07d5b826": "buyAllOutcomes(bytes32,uint256)", "0x5f68804e": "SimpleLotto()", "0xa510f776": "setCompany()", "0x0d48e8d0": "doBalance()", "0xd21d7950": "changeGasLimitOfSafeSend(uint256)", "0x3358d2d3": "buildDSTokenFrontend()", "0xec93cfae": "FountainOfWealth()", "0x65b1fdf4": "scheduleIssuePOIs()", "0xdc3080f2": "spentAllowance(address,address)", "0xd1f0bb2d": "populateAllowedFreeExchanges()", "0xd591221f": "testTransfer()", "0xf24b5779": "removeTrustedIssuer(address,string)", "0xed4b1d0d": "scheduleTransaction(uint256)", "0xa83627de": "updatePeriod()", "0xf597a499": "UserDatabase(uint256)", "0x21f8a721": "getAddress(bytes32)", "0x5548c837": "Deposit(address,address,uint256)", "0x55b775ea": "setFeed(address)", "0x01b869f1": "release(uint32,uint32,uint32,bytes)", "0x609ff1bd": "winningProposal()", "0xdf98ef33": "getResource(bytes,uint256,bytes)", "0xd39eb301": "getStatus(uint8,uint8)", "0x2cc0b254": "init(address,bytes32)", "0x4228974c": "Videos()", "0xc431f885": "addToContribution()", "0x00e43ee9": "setMigrationStatus(uint256,address)", "0xd9f8a4e2": "calcCurrentTokenPrice()", "0xb9e6f1d9": "get_amount()", "0x6b3a87d2": "WatchWinningPot()", "0xef4592fb": "getResult(bytes)", "0x41b9dc2b": "has(bytes32,bytes32)", "0x3b9901cc": "getChannelsByRanks(address,uint256,uint256)", "0x83876bc9": "newProposalInWei(address,uint256,string,bytes)", "0x50a3bd39": "enterPool()", "0xc976bbbb": "_compare(int256,bytes2,int256)", "0x0e47c76f": "rotate(uint64,uint256)", "0x6f85c7e4": "WAITING_PERIOD()", "0x7075b1d8": "latestMonarchInternal()", "0x9209b3c0": "getCrtDetails(bytes)", "0x305075db": "NormalizeRanks()", "0xb6b55f25": "deposit(uint256)", "0xf09ea2a6": "offer(uint256,address,uint256,address)", "0xf1320af2": "exempt(address)", "0xc813c30e": "testThrowSomething()", "0x4faa2d54": "getTimeElapsed()", "0x22017c5f": "DSTokenBase(uint256)", "0x6637b882": "setDao(address)", "0xd0b52156": "getIpfsHash(address,address)", "0x13bd4e2c": "_prepareAndSendReward()", "0xdb7ca38a": "XaurmProxyContract()", "0x1bad1d2e": "monitorWallet(address)", "0x691f3431": "name(bytes32)", "0x3169ff3e": "LooneyLottery()", "0x446a7974": "Fokitol()", "0xdc3ab866": "checkEarnings(address)", "0xfad9bf9e": "storeBlockWithFeeAndRecipient(bytes,int256,int256,bytes,int256,int256)", "0xaf408d89": "setStatus(bytes)", "0xd02bf162": "spinTheWheel()", "0x9a36f932": "feeDivisor()", "0xca77ab8a": "getNextFile(bytes)", "0xc8e49707": "activateExportFee(address)", "0x502414e4": "marketMaker(string)", "0x78ec81a0": "sendEarnings(address)", "0x14167bf0": "oraclize_query(string,string[])", "0xba13a572": "lottery()", "0x299ed37a": "emergencyCall()", "0x6ec3af26": "addTrustedIssuer(address,bytes)", "0x3d69b403": "isOutcomeSet(bytes)", "0x9a19a953": "setInt8(int8)", "0x7817a60f": "acceptMember(address,string)", "0x1e223143": "getFirst()", "0x5437b39b": "hasUnprocessedDividends(address)", "0x8f0c724c": "setOperationsCallGas(uint256)", "0x3c925f16": "getAccountHolder()", "0x477801b1": "getLastRoundResults_by_index(uint256)", "0x3d21aa42": "sendApproval(address,uint256,address)", "0xd2b8035a": "draw(uint256,uint256)", "0x19c32e0b": "hmacsha256(bytes,bytes)", "0x28f90e4b": "Etheramid2()", "0xe87df70e": "fivetimes()", "0xc028df06": "offer()", "0xacb6c69b": "setTrustedClient(address)", "0xac7ffae3": "updt(uint256,string,uint256,uint256,string,string,address)", "0x0e5ffb3c": "hashVersion(uint32,uint32,uint32,string,string)", "0x4ac6b2be": "getCheckRecordCreator(bytes)", "0x93dafba2": "getSubpot(uint256)", "0x592685d5": "getWindowStart(address,address)", "0xc24a0f8b": "endDate()", "0x3f9b250a": "getDocument(uint256)", "0xd845a4b3": "request(uint256)", "0xa5bfa9a9": "claimToken(bytes32)", "0x8e3957d9": "RandomNumber()", "0x62fb09b2": "getRefDescr(uint256)", "0x9f35d3b2": "start(string,string,uint256,uint256,uint256,uint256)", "0xea1bf386": "getNextSellerBOTdata(uint256)", "0xe27fe50f": "startAuctions(bytes32[])", "0x031d973e": "closeMarket(bytes32)", "0x754dea40": "setBackendOwner(address)", "0xd83a8d11": "testProposing()", "0xf24a534e": "Oracle()", "0xd08275f1": "WolframAlpha()", "0x932db761": "profitsFromBitnationDebitCard()", "0xd96aee49": "MultipleConstructorTest()", "0x4594d06a": "delMinter(int256,address)", "0xb9f256cd": "newProposalInEther(address,uint256,string,bytes)", "0x1d2bca17": "MyToken(uint256,string,uint8,string)", "0x6edbd134": "hasHash()", "0x847f8a10": "Refund(uint32)", "0xb0604a26": "schedule()", "0x6676871d": "reserved_funds()", "0x05888fcd": "tradeBalances(address,uint256,address,uint256,address,uint256)", "0xe771066f": "marriageProof(bytes)", "0x7e32a592": "repairTheCastle()", "0xbab2f552": "currentCycle()", "0x45a3b0bf": "resolveFailPledge()", "0x070a888f": "updateRewardDuration(uint256)", "0x20d9822e": "setAnyoneCanCall(address,string,bool)", "0x224993c2": "setTimeBlock(uint256)", "0x01bd4051": "disown(string)", "0x4bc2a657": "setVoter(address)", "0x522103fa": "changeUnicorn(uint256,address)", "0xc988d70f": "getDailyWithdrawLimit()", "0x2f7f3ecf": "findNextHour(uint256,bytes)", "0x1b769e74": "testThrowsRestartNotUpdatable()", "0x4d561721": "etherandomSetNetwork()", "0x92a781d8": "changeBaseValue(uint256)", "0xf0a78538": "scheduleTransaction(uint256,bytes)", "0x64ef212e": "proxyTransferWithReference(address,uint256,bytes32,string)", "0x97c3ccd8": "ban(address)", "0xdeb6930c": "PriceTicker()", "0x6cc5fdaa": "setBytes32(bytes,bytes)", "0x92c8eb96": "DSFalseFallbackTest()", "0x6534b4e2": "IsPayoutReady__InfoFunction(bytes32)", "0x15e812ad": "getBaseFee()", "0xf5b53e17": "getInt256()", "0x081bf263": "isOOB(uint8,uint8)", "0xd2fb8787": "recordExists(bytes)", "0x86c57fcc": "b32ToBytes(bytes)", "0xe3a199d6": "testThrowCreateNewRevisionNotUpdatable()", "0x57cfeeee": "transfer(address,uint256,bytes32)", "0xb72e717d": "fromAddress(address)", "0x61b20d8c": "retrieveFunds()", "0xf4b103d4": "SimpleStorage(uint256)", "0xe2056c46": "ExtraBalToken()", "0xc3ee6311": "lockAndCall(string)", "0x136af582": "next(bytes,bytes,bytes,bytes,bytes,bytes,bytes)", "0xc5d5997c": "changeSubUser(address,address)", "0xf8018a79": "prepend(address,address)", "0xf6b4dfb4": "contractAddress()", "0x9450b1c8": "addCharityFundation(string,string,string)", "0xd1a8d447": "get_all_bet_values()", "0x66d8c463": "reveal(bytes32,string)", "0xbb963c8a": "transferLibOwnership(bytes,address)", "0xd56b2889": "finish()", "0x9fb25d9e": "LeaderMessage()", "0x6ad50ed4": "investmentEntryInfos()", "0xa4d575ce": "_forward(address,bytes)", "0xa3ec5616": "next(bytes,bytes,bytes,bytes,bytes,bytes,bytes,uint256)", "0x0db73c72": "noevent()", "0xf449619e": "collectPrize(uint256)", "0x1afccfa5": "Proposal(address,address,address,bytes,bool)", "0x4788cabf": "getContractId()", "0x4112987c": "strConcat(string,string,string)", "0x928a00d2": "deleteCoin(uint256)", "0xd9ec0508": "testThrowTransferNotEnabled()", "0x9aaf442c": "applyCensorship(uint256)", "0x49407a44": "claimEther(uint256)", "0x4fcf8210": "eraseRecord(bytes32)", "0x5ed84aa6": "getNymCenterAPIURL()", "0xa3c2c462": "totalReceived()", "0x5938748e": "changeVotingRules(address,address,uint256,uint256,uint256)", "0x6fc9d5e4": "changeCompareTo(uint256)", "0xe2ee9941": "tap(bytes20)", "0x0ff4f160": "oraclize_query(uint256,string,string[1])", "0x43ec3f38": "toSliceB32(bytes32)", "0x3288eb0b": "ChineseCookies()", "0x373c98a2": "authCall(address,bytes32)", "0x6ea056a9": "sweep(address,uint256)", "0x8dc45377": "getDuel1(uint256)", "0xd4625a3a": "equals()", "0x616fca9b": "adopt(address)", "0x32cea83e": "birth(bytes)", "0x002a5cc9": "getTicketHolders(uint256)", "0x31119b4d": "changeDeveloper(address)", "0x69569a51": "setFrontend(address)", "0xb7e24979": "addThing(bytes)", "0x164e68de": "withdrawFees(address)", "0x42bf4431": "orderMatchTest(uint256,uint256,uint256,int256,uint256,uint256,address,address,int256)", "0x5dd672ec": "latestBid()", "0x45fe6e2a": "Scheduler()", "0x55cc4e57": "setIssuer(address)", "0x1df5e755": "Etherandom()", "0x8f70009d": "id_for_address(address,address)", "0x39b50688": "cancelSellOrder()", "0x40953102": "scheduleCall(address,uint256,bytes,uint256,uint256,uint8,uint256)", "0x677913e9": "setAmount(int32)", "0x66671c71": "BaseScheduler(address,address)", "0xfe71aec5": "LittleCactus()", "0x879d46fd": "DAOTrust(address,address,bytes,uint256,uint256,uint128)", "0x3b143184": "Congress(uint256,uint256,int256,address)", "0x7370a38d": "getNumPackages()", "0xfee35ff8": "newInvest(uint256,address,uint256)", "0x57dc9760": "DaoChallenge()", "0x8ac78c80": "Docsign()", "0x76d66f5d": "_Transfer(address,address,bytes32)", "0x9dc2c8f5": "fireEventLog4Anonym()", "0x43114842": "acceptChallenge(uint256,uint256,uint256)", "0x2043285d": "getMarketMakers()", "0xfb32f4f5": "ARK_FLAGGER_1_00()", "0x69431ab6": "TokenCreation(uint256,uint256,address,string,string,uint8)", "0x045c6ce0": "voteForProposal(uint256)", "0xa140e79c": "setMinimumDebatePeriod(uint256)", "0x331a72d1": "getRetractable(bytes32)", "0x87cc1e1c": "setExporterBank()", "0x7183616c": "notarize(string)", "0xce79add1": "givableBalanceOf(address)", "0x6f0cfab6": "DNSResolver()", "0x1c2f38ff": "paid(uint64)", "0xb7de47d3": "getIndex(uint256,uint256)", "0x04a2b2c2": "testOwnerCanBreach()", "0x65a4dfb3": "oraclize_query(uint256,string,string,string,uint256)", "0xf2ddc772": "confirm(bytes)", "0xa00ce377": "getIsContractValid()", "0xfe9fbb80": "isAuthorized(address)", "0xbbd39ac0": "coinBalanceOf(address)", "0xa4fde8bc": "player_declare_taking_too_long()", "0xd052fbf6": "getHistory(string,uint256)", "0xd205ad7d": "proposeDissolve(bytes)", "0x5a3b7e42": "standard()", "0xf27197ab": "getIsAvailable()", "0x00a94b6e": "oraclize_query(uint256,string,string[5],uint256)", "0x971c803f": "getMinimumStackCheck()", "0x5168afa4": "getPackageHash(bytes,uint8,uint8,uint8)", "0xaef99eef": "Game()", "0x7f924c4e": "testDeposit()", "0xb1adc241": "BalanceDB()", "0x6a704d7b": "AddedToGeneration(address,uint256)", "0xb9f37c86": "Registrar()", "0xc631b292": "closeVoting()", "0x19350aea": "nameFor(address)", "0x85dee34c": "query2_withGasLimit(uint256,string,string,string,uint256)", "0xbf187478": "shift_left(uint64,uint256)", "0x5b6b431d": "Withdraw(uint256)", "0xe5dd90a5": "HumanStandardToken(uint256,string,uint8,string)", "0xbf8c50ff": "scheduleTransaction()", "0x013d64bd": "setCanCall(address,address,string,bool)", "0xb870ecbb": "testNormalWhitelistAdd()", "0xbcd3d8ca": "Collector(address,address,uint256)", "0x4401ff5c": "sellShares(bytes,uint8,uint256,uint256)", "0x3e0dfbdf": "getInvestorByAddress(address)", "0xcb3e64fd": "unhalt()", "0xafc24e3d": "getChallengeAnswer(uint256)", "0xa36c8ec2": "UpdateContractorAddress(address)", "0xd5a4a3c6": "findRecentBet(address)", "0xb028ee13": "s2b(string)", "0x692ad3a9": "round(uint256,uint256,uint256,uint256)", "0x0a7493b4": "Etheropt(uint256,string,uint256,uint256,bytes,address,int256[])", "0x60689557": "Rock()", "0x717fedf0": "getFirstActiveDuel1()", "0xe837ab59": "getParticipantByAddress(address)", "0x32d5fe98": "revealCampaign(uint256,uint256)", "0x0178b8bf": "resolver(bytes32)", "0x44faa139": "Withdraw(uint32)", "0x418cf199": "setEstimateCost(uint256,uint256)", "0xae45850b": "schedulerAddress()", "0xd4871517": "BTCLotto(address,uint256)", "0xfbffb355": "testBitsEqualFailIndexOOB()", "0x2be6d43c": "ARKTagger_1_00()", "0x1df47aad": "ReplayProtection()", "0xb36a0b15": "getSignDetails(uint256,uint8)", "0x9c4baf27": "Skywalker(address,address)", "0xfb34fc6f": "WatchNextBlockReward()", "0xceba30b5": "scheduleTransaction(address,bytes,uint256[4],uint256)", "0xfc94dd18": "verifyHumanStandardToken(address)", "0x1ff13086": "size(int256)", "0xce92dced": "newBid(bytes32)", "0x231944e2": "moveUnits(uint256,uint256,uint256[])", "0x784813e0": "lookupBet(uint256,uint256)", "0xe0e3ba5a": "getLosesShare(address)", "0x5c1b3ca1": "getConfigUint(int256,bytes32)", "0x91060168": "fetchString(address,bytes4,bytes32)", "0x354b2735": "testDeploy()", "0xb88eef53": "registryCreated()", "0x8e3d4e5e": "Fibonacci(bytes)", "0x64bd87d6": "scheduleCall(address,bytes,bytes,uint256,uint256)", "0x5c3d005d": "demote(address)", "0xb6509c12": "Ethereum_twelve_bagger()", "0xe87508be": "investorDeposit()", "0xcb96012e": "hashTo256(bytes32)", "0x314e0fb6": "scheduleTransaction(address,bytes,uint256[3],uint256)", "0x5b6a54bc": "adjustTransactionFee(uint256)", "0x5f515226": "checkBalance(address)", "0x76cd7cbc": "sign(bytes)", "0xce220ecf": "testAddBalanceFailsAboveOverflow()", "0x922fc84b": "taskProcessedNoCosting(uint256)", "0xf8b2cb4f": "getBalance(address)", "0x7a29332d": "buyAllOutcomes(uint256,uint256)", "0x04106c8b": "startGeneration()", "0x8eaa1e29": "getContentByData(address,uint256,string,string)", "0x5a7a8850": "rollWithSeed(bytes32)", "0x2facc4e8": "depositGovernance(uint256,address)", "0xf2561a43": "voteSuicide(address)", "0xee1b4828": "closeBooks()", "0x18f303a1": "SetInternalValues(uint8,uint256)", "0xce60f78d": "createMarriage(bytes,bytes,uint256,bytes,bytes)", "0x18b749c4": "payEther(uint256)", "0xbbc6eb1f": "getDefaultDonation()", "0x0b1ca49a": "removeMember(address)", "0xccf4f413": "setSubRegistrar(string,address)", "0x4616caa9": "pushCoin(uint256,address,string)", "0xd69450d5": "setUUID4Bytes(bytes)", "0xc98165b6": "createTarget()", "0x8c79a24d": "refName(uint256)", "0x56d88e27": "len()", "0xbfc3d84b": "CT()", "0xc7f2e6af": "Contribute(bytes20)", "0xd7e11e9d": "AddTicket(bytes)", "0x2d06177a": "addManager(address)", "0xd588acc4": "claimMiningReward()", "0x2ffb9e64": "updateGasForXaurData(uint256,uint256)", "0xf896503a": "getConfigAddress(bytes32)", "0x9af8c4ba": "respond(uint256,address,bytes)", "0x82a5285d": "getMinBetAmount()", "0x3ead67b5": "changeContractOwner(address)", "0xb29d7914": "getRefResults(uint256)", "0x135128c2": "CounterPartyDeposit()", "0x795b9a6f": "scheduleCall(address,bytes4,uint256,bytes)", "0x83197ef0": "destroy()", "0x433836dc": "scheduleTransaction(address,bytes,uint8,uint256[3],uint256)", "0xf009347d": "KudosProxy(address)", "0xf2fde38b": "transferOwnership(address)", "0x62c99e84": "_Approval(address,address,bytes32)", "0x1b83b823": "notifyPlayer(uint256)", "0xe56c8552": "spinTheWheel(address)", "0x93eec1fb": "setName(uint8,uint8,string)", "0x11af3c68": "divest(address)", "0x8279c7db": "setReceiverAddress(address)", "0x44691f7e": "hasStarted()", "0x349501b7": "checkDepth(uint256)", "0xe8beef5b": "fireEventLog3Anonym()", "0x0870607b": "addSubUser(address)", "0x063925c8": "scheduleCall(bytes,uint256,uint256)", "0xa23744f0": "tryCreateCheckRecord(bytes)", "0x35d79fad": "CertificationDb(address,uint256,address)", "0x44691f2b": "Dispute()", "0xd7ccc2c3": "getLastPayment()", "0x152583de": "getAttributes()", "0x1a9360dd": "checkDate()", "0x420ef2b3": "TargetHash()", "0xf0caea2b": "SmartRoulette()", "0x5d268629": "Refund()", "0x23385089": "emitApprove(address,address,uint256)", "0x7648c929": "returnRemainingEther()", "0x5d5bc4cb": "BetOnRed()", "0xde8fa431": "getSize()", "0xda6b31b9": "testErrorTransferToNullAuthority()", "0x444dd6f3": "Elcoin()", "0xe1fa8e84": "register(bytes32)", "0x3e0a322d": "setStartTime(uint256)", "0x21bacf28": "getDefaultFee()", "0xb1662d58": "setModule(address,bool)", "0x5b0fc9c3": "setOwner(bytes32,address)", "0x40193d17": "getPongvalConstant()", "0xfb95adeb": "testFailBlockhashInsuffiecientFee()", "0x8ecc0950": "returnToOwner()", "0x34b7ac9b": "END_MINTING()", "0xeaa37394": "create(bytes,bytes32,bool,bool,bool,bool,bool)", "0xd7bc23af": "newParameters(int256,uint256,uint256,uint256)", "0x97709cde": "ARK_VOTER_1_00(uint256,uint256,uint256,uint256,uint256,uint256)", "0xcf4a1612": "scheduleTransaction(uint256,address,bytes,uint256)", "0x40695625": "testRetractLatestRevision()", "0x90c3f38f": "setDescription(string)", "0x5fdf05d7": "two()", "0xaaac50bd": "transferDisable(bytes32)", "0x206a44f3": "getNum(bytes,uint256)", "0x7ac26aeb": "getTag(string,uint256)", "0xdc3f65d3": "createdByMe()", "0xe99543aa": "Trash(uint256)", "0x1bcad37a": "getTotalCost()", "0xa7f43779": "remove()", "0x3416f9d4": "subtractSafely(uint256,uint256)", "0x3bcf7d22": "newBribedCitizen(address)", "0x918f1bb5": "ProjectKudos()", "0xcf09e6e1": "SetBigContract(address)", "0x23add736": "claim(uint256,uint256,uint8,bytes,bytes)", "0x8ac6a869": "isObsolete()", "0x2324c67c": "getAllSignatureHashes(bytes4)", "0x981a60f5": "extractNameFromData(bytes)", "0xc018d0e6": "getFeeAmount(int256,int256)", "0x50e06b57": "Etherization()", "0x49e65440": "setSymbol(bytes32)", "0xfaee13b9": "set(int8)", "0x01ffc9a7": "supportsInterface(bytes4)", "0xc0b92612": "changePig(address)", "0x3f9f5b68": "setPreviousID(uint256,int256)", "0x2e9c5e77": "doStackExtension(uint256)", "0xc83be888": "single_move(uint256,uint8,uint8)", "0xef19c332": "_checkSigned(bytes32,uint256,uint8,bytes32,bytes32)", "0x2f30c6f6": "set(uint256,address)", "0x62986e27": "Canary(address,uint16)", "0x44dfdce0": "getNameOwner(bytes)", "0x4b7fcee7": "ownerPausePayouts(bool)", "0x84dac46e": "Fucksign()", "0x0878bc51": "getAttachesto(uint8)", "0x42d16748": "getMinDailyWithdrawalLimit()", "0x4ecd73e2": "DistributeDividends(uint256)", "0x03750d94": "serverSeed(address,bytes32)", "0xea0a5237": "announce(string)", "0x611daa7e": "EmergencyBalanceReset(uint256)", "0x2ae87a70": "getNumContents(address,uint256)", "0x5ec01e4d": "random()", "0xfd735602": "executeN()", "0xfebefd61": "startAuctionsAndBid(bytes32[],bytes32)", "0x8b64d70e": "owner_set_time_limit(uint256)", "0x2839e928": "ackermann(uint256,uint256)", "0xaf29e720": "remainingGasFund(uint256)", "0x9eee85fe": "bookEarnings(address,uint256)", "0x3733ffca": "convertTo(uint256,string,string)", "0xdbde1988": "transferFromWithoutReward(address,address,uint256)", "0xd3edcb5b": "getCreditorAddresses()", "0x77fcb91d": "forward(address,bool)", "0xf062e26b": "check_darkdao()", "0xdef2489b": "convert(address)", "0xccbda1af": "getChannelByName(string)", "0x267c8507": "authorizeManager(address)", "0xf1c30ec0": "reclaim(bytes)", "0xe6470fbe": "updateDefaultPayment()", "0x8fe58eb9": "Triger()", "0xd6b44859": "scheduleUndoIt(uint256)", "0x0eb8ed07": "transferEnable(bytes32)", "0xfc1f2a70": "Add(uint256,string,string)", "0x058026d0": "checkTransferToICAPWithReference(bytes32,uint256,string)", "0xaf27c7b3": "Security_HasPasswordSha3HashBeenAddedToBankAccount()", "0x5b151fd2": "fifty_fifty()", "0x531d1974": "testThrowRetractLatestRevisionEnforceRevisions()", "0x60213b88": "getInitialWithdrawal()", "0x29e94503": "VersionedBlob()", "0x9c5d7030": "reimburseGas(uint256,address,uint256,uint256)", "0xcd9a3c98": "any(bool[7])", "0xb484e532": "getMyMsg()", "0xd1f59db9": "isLatestMinorTree(bytes32,bytes32)", "0xe0c6190d": "checkTime()", "0xaf5610dd": "isThisPreforkVersion()", "0xfa968eea": "minBetAmount()", "0x003538c5": "TestRegistrar(address,bytes32)", "0x71c59097": "MainnetSurvey(uint256,string,bytes32[])", "0xc82aac47": "searchByTag(bytes32)", "0xeca5c793": "testErrorUnauthorizedNameRegister()", "0xdc75f2db": "multiowned(address[],uint256)", "0x4c9ed763": "requestTokensBack()", "0xbd9a5673": "oraclize_query(string,string[5])", "0xb11e3b82": "createEvent(bytes32,bool,int256,int256,uint8,address,address,bytes32[])", "0x86e4e178": "CheckTickets(address,uint256,uint256)", "0x53c84526": "setSmartAffiliateContract(address)", "0x89029d8c": "get_all(uint256,uint256)", "0xc25e6908": "ultimateOutcomes(bytes32)", "0x6b1e564a": "challengeWinningOutcome(bytes32,uint16)", "0x1c895915": "getNumberOfPayments(uint256)", "0x244fcd03": "removeRelease(bytes32,string)", "0x2ca6d2c0": "getAccountSize(address)", "0xfe67a54b": "endAuction()", "0x756fb8c9": "getOptionChain()", "0x88102583": "safeCastSigned(uint256)", "0x60063887": "transferDebt(address,address,address,uint256)", "0x16216f39": "return13()", "0x8089d001": "getHashOfBlock(uint256)", "0x27bc39c0": "submitCanonicalCandidate(bytes,bytes,bytes,bytes)", "0xb29ae23f": "getDateOfSignature()", "0xdd729530": "add_shield(uint16)", "0x38557648": "executeSellOrder(address)", "0xe2a71f12": "accountDelete()", "0xce869a64": "fails()", "0x69bdfd3a": "toContractDie(bytes,bytes,uint256)", "0x99aeade3": "iterateTable(uint256,uint256)", "0x46bdca9a": "equal(string,string)", "0x5e07f240": "shiftBitsLeft(bytes,uint256)", "0x76285b5b": "_is360thDay()", "0x594151e0": "Dice()", "0x4dc43eaf": "setTreasury(uint256,uint256)", "0xec727000": "getApprovalDB()", "0xb144adfb": "balance_of(address)", "0x63052d82": "getOwnersIndex(address)", "0x833b4596": "testApproveSetsAllowance()", "0xc67d376d": "getClosedCandidates()", "0xcf03f5f4": "activateMasterKey(address)", "0x7ca55e00": "etherandomVerify(bytes32,bytes32,bytes32,uint256,uint256)", "0xd92ebe46": "createDAO(address,uint256,uint256,uint256,string,string,uint8)", "0x3773930e": "ConfigureFunction(address,uint256,uint16,uint16,uint16)", "0xfee6d28c": "addSnapshot(string)", "0xe71264fa": "addNewTokens(uint256)", "0xc4d9102f": "setNextID(uint256,int256)", "0xa6f0e577": "isLeapYear(uint16)", "0xc7102df7": "__stopBlock()", "0xfa93019c": "getBlocks(uint8,uint8)", "0xe9794dc1": "CreateHash(uint8,string)", "0x5460ef10": "sendWithExtraGas(address,uint256,uint256)", "0x2f62a6ff": "fipsRegister(uint256,address,bytes)", "0xd630bd53": "pledgeApprove(uint256)", "0x3448c7d6": "createHistory(bytes,address,address)", "0xdda3342b": "ReplicatorFactory()", "0x0cd865ec": "recover(address)", "0xb9f28076": "historyIdx(address)", "0x4dda1764": "CafeMaker()", "0x883ba26b": "getIsSettled()", "0x3f5e268f": "convictInitial(uint256,uint256)", "0x4a3b0eec": "authorizeOpen(uint256,bool,string)", "0xee6d2641": "sendWithExtraGasExt(address,uint256,uint256)", "0xde14bbf7": "randomGen(uint256,uint256)", "0x7f3bd56e": "disburse(address,uint256)", "0x20d8741f": "Feed()", "0x60c6b3a5": "claim(bytes,address,uint256,uint8,bytes,bytes)", "0xda4b5e29": "contains()", "0xb3c25835": "addUser(address,string,string,uint256)", "0xee2af3fb": "set_factory(address)", "0xb821f815": "pay_winner(uint256)", "0x138cc941": "testErrorTransferToRejectAuthority()", "0xc0b6f0c2": "NextRoundAndEvents()", "0xc7e67360": "GAS_BUFFER()", "0x058d7433": "setAlliesContract(address)", "0xd810f298": "computeSettlementAmount()", "0xa24d23eb": "ProcessGame(uint256,uint256)", "0x7ac91cc2": "testFailOwnedAuth()", "0x79c3ddc1": "isPackageOwner(string,address,address)", "0x478ae93c": "playToWin(uint256)", "0x6632a507": "testSetupPrecondition()", "0xb6013cef": "finalize(uint256,uint256)", "0x37b7bf11": "Tile(int256,int256)", "0xecfc7ecc": "placeBid()", "0x70b1d9d4": "requestCanonicalFormat(bytes)", "0x315fdea3": "TreasureChest()", "0xc5575ef0": "checkTransferFrom(address,address,uint256)", "0x65c72840": "getDay(uint256)", "0xd6eafd08": "scheduleCall(address,bytes,bytes,uint8,uint256[4])", "0x350fbe2e": "calcNextDrawTime()", "0x8af784dc": "expectEventsExact(address)", "0x2db89533": "Auth(uint8,address)", "0x9f203255": "setAuditor(address)", "0x2526d960": "clawback()", "0x3fbd40fd": "ProcessDraw()", "0xface030b": "SpinTheWheel(address)", "0x648621ec": "xnotify(string)", "0x22dc36e2": "processed(uint64)", "0x6f52167d": "payDuel(address,string,address,string)", "0x8f70bfa0": "processDeposit()", "0x25ea269e": "Scissors()", "0x93feb13b": "ForceSendHelper(address)", "0xb688a363": "join()", "0x89859b50": "updateLatestTree(bytes32)", "0xf83d08ba": "lock()", "0x7d287697": "testTryGetUnset()", "0x98d5fdca": "getPrice()", "0xfe72e717": "toDie(bytes)", "0xb3c06f50": "transferFrom(address,address,bytes32)", "0x1465aa97": "testingContract()", "0x069d6d1c": "closeOrder(uint256)", "0xa79f26dc": "force()", "0xf2371fb3": "grantGiveableKudos(address,uint256)", "0xaa7dcd84": "testUpdateAuthorityEvent()", "0x0d8b5fa2": "testControllerValidTransferFrom()", "0x0e0f55d0": "RewardOrder(uint256,uint256)", "0x9ea1b79d": "getContentChannel(uint256)", "0x4a67fa7d": "setLotteryFee(uint256)", "0xdb006a75": "redeem(uint256)", "0x8f4ed333": "step2()", "0x1a10cfc3": "delete_entry(uint256,uint256,uint256)", "0xd422e4e0": "takeFee(address,uint256,string)", "0x61a00f6d": "Ballot(bytes32[])", "0x9c30936f": "removeCertificationDocumentFromSelf(bytes32)", "0xa5f4af33": "playerWithdrawPendingTransactions()", "0x07ad9ecb": "safeSend(address,uint256)", "0x8f99ea43": "setDividendDB(address)", "0x1df473bc": "newContract(bytes)", "0xea5ea470": "payFunding(uint256)", "0x743e0c9b": "receiveTokens(uint256)", "0x21835af6": "__dig(uint256)", "0x47448e8a": "set(bytes32,string,bytes32)", "0x9b1ad792": "destroyToken(address,uint256)", "0xf765088f": "UpdateClientAddress(address)", "0xddbbc35c": "searchByName(string)", "0x5ed7ca5b": "halt()", "0x97950740": "roomForBirth()", "0xfc01abbe": "stringToBytes32(string,string)", "0xea3d508a": "selector()", "0x8c88752a": "ContributorList(uint256)", "0x5837e083": "move_history(uint256)", "0xf7c3ee7a": "immortality()", "0x1b9f9647": "accessMyWallet(address)", "0xc8691b2a": "getHistory(uint256)", "0x91e8d3dc": "testBitOrFailIndexOOB()", "0x5c89c10d": "setBannedCycles(uint256[])", "0x4500054f": "isCancellable()", "0x334ef224": "testThrowsUpdateLatestRevisionNotOwner()", "0x763a738c": "allNames()", "0x45590ec8": "addTag(uint256,string)", "0xe7740cf9": "revealPaper(string)", "0xd9d2d058": "Splitter()", "0xb412d4d6": "CafeDelivered()", "0x8365172c": "num_levels()", "0x41c0e1b5": "kill()", "0x3106fea0": "voteOnProposal(uint256,bool,uint256)", "0x82ab890a": "update(uint256)", "0x4636a159": "newPhoneToAddr(address,uint256)", "0x2f29d8c5": "elapsed()", "0x1bf20668": "testAdminTransfer()", "0xf709dd51": "getTrademark()", "0x8b859409": "setRelease(bytes32,bytes32,string)", "0x03959bb7": "setDataContract(address)", "0x4247f52d": "DoRoll()", "0x31ab4066": "testAuthorityTryAuth()", "0xac4b2bae": "newParameters(int256,uint256,int256,uint256)", "0x57eaeddf": "_isContract()", "0x4a3a87e2": "CreateProxyWithControllerAndRecoveryKey(address,address,uint256,uint256)", "0xd116c8c4": "releasePayment()", "0x6615dd83": "setSeedSourceB(address)", "0xb8aca90b": "CurrentGame()", "0xc124e2ea": "checkBetDozen(uint8)", "0x4b0bbf84": "addEntropy()", "0x452fbc41": "USN(address,address,bytes,uint256,uint256,uint128)", "0xcdb6753b": "setNav(uint32)", "0xbb5d40eb": "isValid()", "0xd6f42038": "PhoneToAddress()", "0x6bc3e0f0": "verifySecondHalf(uint256[4],uint256[4],uint256[4])", "0x33893071": "checkMyWithdraw(address)", "0xfb46d4c5": "tweet(string)", "0x248582b0": "receivePaymentForGoodsSoldEarly()", "0x766a3f2e": "Security_ConnectBankAccountToNewOwnerAddress(uint32,string)", "0x1c8d5d38": "allowance(address,address,bytes32)", "0x6b256f57": "DAOSecurity(address,address,bytes,uint256,uint256,uint128)", "0xe8d1e961": "lockAccount(uint256)", "0x152fb125": "SimpleMixer()", "0xf72457af": "CertifierDb()", "0xe8a5282d": "setConfig(bytes32)", "0xbeb92f55": "setCaller(address)", "0x9a571d9f": "isAlphaLower(bytes1)", "0x46a2679a": "getSubpotsCount(uint256)", "0xd62d3115": "testCreate()", "0xb6ed0632": "cancelOrder(uint256,uint256)", "0xc95e81cb": "MyBet(uint8,address)", "0x1d5a9f3f": "object_types(uint256)", "0xa49d53a1": "SmartRevshare()", "0x5b65b9ab": "setFee(uint256,uint256,uint256)", "0x116c6eab": "getProfitShare(address)", "0x8e46afa9": "getDefaultGracePeriod()", "0xdabc706e": "getProposalCost()", "0x3fbb539d": "scheduleCall(address,bytes,uint256,bytes)", "0x86269a88": "checkBetNumber(uint8)", "0xb6ac24df": "updatePatchTree(bytes32)", "0x4637d827": "trust(address)", "0x1c1b8772": "update(address)", "0x5a9f2def": "scheduleCall(bytes4,bytes,uint256,uint256)", "0x81a60c0d": "getResults(uint256)", "0xd1b4ff7e": "multiAccessRevokeD(bytes32,address)", "0x92b7d5b9": "getCurrentGaslimit()", "0x77ceded8": "mintGrey(int256,address,uint256)", "0x2a095fbe": "unlinkEID(bytes,bytes,address)", "0xa6e16ba2": "testThrowsRetractLatestRevisionNotOwner()", "0x4579268a": "getOffer(uint256)", "0xcabb3a3a": "isAlphaNumeric(string)", "0xfadc51cf": "isAlpha(bytes1)", "0xf2022905": "toldYouItWouldWork()", "0x686e8aaa": "GetMoney()", "0x07718a3b": "BankOwner_WithdrawDonations()", "0xc58343ef": "getRequest(uint256)", "0x7b1a547c": "registerAs(address,string,uint256,string,address)", "0x213b9eb8": "setAddr(string,address)", "0x75090ebf": "changeDomain(uint256,uint256,uint256,address)", "0xdbbdf083": "register(uint256,address)", "0xfa4e5e5a": "notify(uint8,string,string)", "0x86a5ff97": "changeStatus(string)", "0xb8f71f26": "scheduleTransaction(uint256,address)", "0xa2ec191a": "addDSource(string,uint256)", "0x18b31f94": "registerLengthFunction(string,string,address)", "0x7b395487": "voteForUltimateOutcome(bytes32,uint16)", "0x39246d75": "VersionModel()", "0xd500dd6a": "challengeTimeout(uint256,bool,address)", "0xd1da09ee": "extractImportFeeChargeLength()", "0xc74e907b": "commit(address,uint256,uint256)", "0x4b09ebb2": "e_exp(uint256)", "0xec3af4a9": "getProjectKudos(address)", "0x714064f3": "BreakableBond(address,address,uint256)", "0xc4bc5da5": "resumeContract()", "0xf7888aec": "balanceOf(address,address)", "0x2f597e71": "testLongInput()", "0x7212b67e": "add_potion(uint16)", "0x9a15f4f3": "getBlockHeader(int256,int256)", "0x6eacd48a": "ownerPauseGame(bool)", "0xf739ed4c": "id_for_user_version(uint256,uint256)", "0xfaf0952b": "testThrowRestartNotOwner()", "0x88a1e895": "test2Fails()", "0x237e9492": "executeProposal(uint256,bytes)", "0x7cb97b2b": "set_owner(address)", "0x2bb685bc": "kill2()", "0xdc52696f": "tokenSupplyChanged()", "0x83d852d9": "shutdownTransactions()", "0x525b25b1": "getDeploymentReward()", "0xeac116c4": "createKingdom(string,address,address,address,address)", "0x014e5fde": "ARKController_1_00()", "0xc6ae3b57": "dEthereumlotteryNet(address,address)", "0xcddbe729": "game(uint256)", "0x8823a9c0": "changeFeeTake(uint256)", "0x021991e7": "getBetsLocked()", "0x3015394c": "cancelRequest(uint256)", "0x9d118770": "destroy(uint256)", "0xe854dfb4": "Order(address,uint256,uint256)", "0x8435be4b": "getLastFarm(uint8,uint8)", "0x27fbcac5": "getChannelFeed(address,uint256,uint256)", "0xc1be4031": "XaurumProxyERC20()", "0x8e25071a": "setProxyCurrator(address)", "0x4f139314": "compensateLatestMonarch(uint256)", "0x85c78fac": "retryOraclizeRequest(uint256)", "0x478e25bf": "resetAction(bytes32)", "0xc74c251f": "addSafely(uint256,uint256)", "0x058aace1": "divest()", "0x6d1da953": "createWithNonce(bytes32,bytes)", "0x30c0f8d6": "scheduleTransaction(address,bytes)", "0x69a5e902": "multiAccessCall(address,uint256,bytes)", "0x6f8b44b0": "setMaxSupply(uint256)", "0x919edc7c": "getChainySender(string)", "0x0b7ad54c": "getContent(uint256)", "0x5bfdc700": "registerData(address,int256,bytes,address)", "0x0d1fce42": "getBankroll()", "0x739b47ca": "recordWin(address)", "0xa5ea11da": "getParameters()", "0xf8af9e6f": "setAdv(uint256,string,string)", "0xe32e9f22": "setDeploymentReward(uint256)", "0x0baaaed9": "setConfigBytes(bytes,bytes)", "0x99f4b251": "mine()", "0x362af076": "createRequest(address[3],address,uint256[11],uint256,bytes)", "0x7fd238ba": "doCoinage(address[],uint256[],uint256,uint256,uint256)", "0x3adb2de7": "bet_this_spin()", "0xa311dd70": "setArray(uint8[10])", "0xc5bf339c": "getLastNonPublished()", "0x9d1bbd7e": "CancelRoundAndRefundAll(uint256)", "0x89790192": "WithFee(address,uint256)", "0x1c879c47": "getMarketHashes(bytes)", "0xbb84d362": "splitProfitVIP_only_Dev()", "0xffb1a6cb": "getWins(address)", "0x3b355af6": "baseData()", "0xb181a8fc": "resetContract()", "0x7d3d6522": "goalReached()", "0xd4c2b6b1": "scheduleTransaction(address,bytes,uint256[5],uint256)", "0xd65ab5f2": "startGame()", "0x4c4766e8": "KittenRegistry()", "0x77e5bf84": "getTxGasprice()", "0xff981099": "getVotes(uint256)", "0x4a7b26ec": "join_game(uint256)", "0xcccf7a8e": "has(uint256)", "0xa525f42c": "transferFromToICAP(address,bytes32,uint256)", "0xeef8e35f": "setChainyURL(string)", "0x557ed1ba": "getTime()", "0x595da94d": "has_owners(uint256)", "0x12511c14": "transferEnable(bytes20)", "0x2b291eb6": "UserAddTicket(bytes)", "0x50baa622": "withdrawToken(uint256)", "0xc01a8c84": "confirmTransaction(uint256)", "0x671dacdc": "CalculateSqrt(uint256)", "0xe74ffbd5": "getPart(bytes32,uint256)", "0xdd54a62f": "content(string)", "0x4025b293": "redeemAllOutcomes(bytes32,uint256)", "0xa8659216": "setInitialLockinDays(uint256)", "0x00b5b223": "computeResponse(uint256,uint16)", "0x2ef761d3": "buyTile(uint8,uint8)", "0x0a874df6": "lookup(uint256)", "0x42c69566": "get_address(address,string)", "0x02dc2e1d": "queuePayment(bytes)", "0x86bb7121": "getBlocksPerRound()", "0xacfdfd1c": "deploy(uint256,string,string,address)", "0x7d298ee3": "beforeExecute(address,uint256)", "0x5023d124": "TestFactory()", "0x827ef325": "_parseMsgData(bytes)", "0xd35b9d83": "codeAt(address)", "0x26161670": "donkeyRanking(uint256)", "0xe0834ea4": "WatchBalanceInEther()", "0xd44f2d3d": "getInitialWithdrawalDone()", "0x4f223fe3": "StatefulFactory(string,string,string)", "0x91cd242d": "setMeta(bytes32,bytes32,bytes32)", "0x9a97043b": "depositIdx(address)", "0x85db2dda": "PayoutQueueSize()", "0x423e1298": "setDoNotAutoRefundTo(bool)", "0xb7a97a2b": "isValidChannel(uint256)", "0xc1441172": "setBlackFlagRequest(uint256,uint256)", "0x53d9d910": "create(address[],uint256,uint256)", "0x64d905c0": "awaitingParticipants()", "0x718bd6dd": "setRequestUntil(uint8)", "0x5a353193": "KrakenPriceTicker()", "0xfb099c84": "newInvestor()", "0xd264e05e": "forward()", "0xcd9f05b8": "balanceEtherAddress(address)", "0xa1da2fb9": "retrieveDAOReward(bool)", "0x60708ae3": "issueAndCommit(address,address,uint256,uint256)", "0x109df68e": "rotateBitsRight(bytes,uint256)", "0x793cd71e": "cashOut()", "0xd3017193": "addUser(address,uint256)", "0xaacf5328": "setVideoID(string,uint256)", "0xb56e1bca": "setExchangeToken()", "0x9341231c": "sendOrThrow(address,uint256)", "0xaed8f3da": "partsPerBillion(uint256,uint256)", "0xdcff5581": "NewFeeAddress(address)", "0xbbe4fd50": "getNow()", "0x3df16377": "make_move_and_claim_victory(uint256,uint8,uint8,uint8,uint8,uint8,uint8,uint8)", "0x5ae5df8f": "deleteRef(string)", "0x6d853ab6": "isSubUser(address)", "0x28472c6c": "claimComputation(bytes,bytes)", "0x2c215998": "updateStatus(string)", "0x7eff1465": "setAccountAllowance(address,address,uint256)", "0xd5089396": "Token(string,string,uint8,uint256)", "0xd1f7a4e4": "createCertificate(bytes)", "0x8c0e2a31": "regProxy(address)", "0xa819819b": "sweepDeityCommission(uint256)", "0x2b861629": "storeBlockHeader(bytes)", "0x25d4bdeb": "LookAtCollectedFees()", "0x5dddea66": "updateState(uint256,uint8,uint256)", "0x3ccfd60b": "withdraw()", "0x6795dbcd": "getAddress(bytes32,string)", "0x9b9ba572": "oraclize_query(string,string[3])", "0xa925d85e": "Exchange(address,address)", "0xbfe8c107": "betOnDozen(bool,bool,bool)", "0x1af716ba": "transferFrom(address,address,uint256,string)", "0x67eae672": "sendCoinFrom(address,uint256,address)", "0x311d5a2a": "recordBalance(address)", "0x7ca823d5": "getAverageChainWork()", "0x19483cd1": "checkHash()", "0xd366fbab": "startLottery(bytes32,uint256,uint256,uint256,uint256,bool)", "0x4d70d1d7": "generateId(uint256)", "0xe13dc28b": "testValidTransfers()", "0x12065fe0": "getBalance()", "0xdd67a360": "OrderLifeCycle()", "0xd7c23572": "historyTimesPlayed(address)", "0x2675c123": "CloseContract()", "0x1381e400": "cancel(uint32)", "0xa48a663c": "transferFromToICAPWithReference(address,bytes32,uint256,string)", "0xb03260be": "scheduleTransaction(uint256,address,bytes)", "0xb37217a4": "getRandomNumber(uint256)", "0x5c54305e": "InsufficientFunds(address,uint256,uint256)", "0x17e1b09b": "minimumDeposit(uint256)", "0x10c4e8b0": "all()", "0xa31d5580": "Registrar(address,bytes32,address)", "0xbe6307c8": "getDraw(uint256)", "0xc985c221": "get_all_levels()", "0x91b7f5ed": "setPrice(uint256)", "0xe42def21": "CryptoHill()", "0x738486bd": "BeerCoin(uint256)", "0xe422ebe9": "getBot()", "0x67dd74ca": "buyTicket(uint256)", "0x276b94e1": "copypaste()", "0x39aaba25": "get_status()", "0x7ed19af9": "multiAccessRevoke(bytes32)", "0x4c1b2446": "transmitInteger(address,bytes,bytes,uint256,uint16)", "0xd014c01f": "enter(address)", "0x1d49e081": "EXECUTE_EXTRA_GAS()", "0x9dafbc13": "initBlock(uint256)", "0xc7e22ac4": "setOracleGas(uint256)", "0xa3053236": "SafeInvestments()", "0xf42ac1de": "minQuorum(uint256)", "0x04d91c6a": "testFail()", "0x0e662cf0": "buyTokens(uint16)", "0x1ef0625b": "player_2(uint256)", "0xcec1365a": "ShortLimit(uint256)", "0x340f5e4e": "get_all_num_levels()", "0x3e2729bf": "isRevocated(bytes)", "0x5a1cc358": "getChannelRank(address,uint256)", "0x4d366398": "runPeerBalance()", "0xaf9a3f9b": "hashName(string)", "0x33298e25": "invoke(uint256,uint256)", "0x63def590": "untrustClient(address)", "0x836d6d66": "WeeklyLotteryB(address,uint256)", "0x7f497550": "scheduleTransfer(address,uint256,uint256)", "0xf9e05ed9": "sha(uint128)", "0xf6458c6a": "toZ1(uint256[3],uint256)", "0xf41017fc": "finalize(uint24)", "0xeeb57139": "CollectMoney(uint256)", "0xfdacd576": "setCompleted(uint256)", "0xb7266456": "StandardToken()", "0x6a8c2437": "totalRescues()", "0x1fdf6e0c": "protectKingdom()", "0xcf31e9fe": "getOutputHash()", "0xc8117b5b": "extractBalanceOfLength()", "0x0674763c": "assert(bool)", "0x87def081": "getFeeRecipient(int256)", "0xc63ff8dd": "claim(bytes)", "0x329bfc33": "getCurrentWinner()", "0x3d6a3664": "setNewOracle(address)", "0xdaa21e0e": "testBitSetSuccess()", "0xbf8783e0": "callAndGetReturn(address,bytes,uint256)", "0xb06df18e": "transfer(bytes20,address)", "0x00100a18": "NewPoll(string,string,uint256,uint256)", "0x2ffb8631": "getReleaseLockfileURI(bytes32)", "0x6716a692": "setDVIP(address)", "0xe8223468": "sha3clone(bytes)", "0x0aeacb5e": "getTotalRecords()", "0x29e30910": "testThrowCreateExistingNonce()", "0x240ecad5": "transferViaProxy(address,address,uint256)", "0xa33d4968": "Tripler()", "0x8caaaae6": "totalWeiPrice()", "0xf28a7912": "quick2()", "0xcbe9ef39": "BasicCoin(uint256,address)", "0xea3d2827": "selectWinner(string)", "0x92e9fd5e": "ColdWallet(address,address)", "0xcd5e3c5d": "roll()", "0x4a82534b": "create(address,address,address,uint256,uint8,uint8,uint256)", "0x5ccd2f9b": "_deleteAllPackedRevisionBlockNumbers(bytes20)", "0x9380b8e7": "testFailAddingMembers()", "0x31b0795c": "registerAddress(address,address)", "0xb76e4890": "Tester()", "0x29c08ba2": "payPremium()", "0xd7f31eb9": "forward(address,uint256,bytes)", "0xd7ef1356": "best_adjustment(bool)", "0x48d9a374": "blockTransfer(address,uint256)", "0x88b9e10e": "seizeTokens(address,uint256)", "0x8736fd16": "getRefStatus(uint256)", "0x2b30d2b8": "invoke(uint256)", "0xd4e78272": "Draw()", "0x0257c48c": "meta(bytes32,bytes32)", "0xc1246d39": "simulatePathwayFromBeneficiary()", "0x699b328a": "randomize()", "0xa200dc73": "getNextShareholder(address)", "0x9a7a7c11": "makeRoll(uint256)", "0x2bf1f9da": "restart(bytes32,bytes)", "0x943a32bc": "Relay(address)", "0x93503337": "isAllowed(bytes32,uint256)", "0xe97b2190": "add_wall(uint16)", "0x0448f79f": "addOptionChain(uint256,string,uint256,uint256,bytes,address,int256[])", "0xc0df77d0": "getRefName(uint256)", "0x27121069": "verify(bytes,uint8,bytes,bytes)", "0xb7009613": "canCall(address,address,bytes4)", "0x0295d71b": "currentDepositLimit()", "0x35ee2783": "Alarm()", "0x71b6663e": "play1(address,uint256)", "0x0178fe3f": "getData(uint256)", "0x489306eb": "oraclize_query(string,string)", "0xfce59d0c": "MangoRepo()", "0x8efc777f": "isBeta(bytes)", "0x1f7b8622": "getVotingDeadline()", "0x76da5667": "admin_kill()", "0x152cf9db": "getDataPoint(int256,uint256,uint256)", "0xd09de08a": "increment()", "0x64ee49fe": "scheduleCall(address,uint256,bytes4,uint256,uint256,uint8)", "0xd9fe60f3": "DTHPool(address,address,uint256,string,string,string)", "0x6b4dd158": "getPrice(bytes)", "0x0a80ef45": "getIsClosed()", "0x51fdaf92": "checkExpiredfunds()", "0x694e0d5b": "StringPasser(uint8[])", "0x0e1d88fc": "addTender(uint256,uint256,address,uint256)", "0x53850db3": "getParticipantById(uint256)", "0xb6cb405b": "getContractor()", "0x4c6b25b1": "results(bytes32)", "0x1c4e6cd0": "NameReg()", "0x53aab434": "buyIn()", "0x1ebe5c0f": "sendWithAllOurGasExcept(address,uint256,uint256)", "0xd98b9bb5": "placeBid(address,uint256)", "0x02e8d8c0": "scheduleTransaction(address,uint256,uint256)", "0x8a120dc9": "testBitEqualFailIndexOOB()", "0x33f707d1": "ownerWithdraw(uint256)", "0x98e00e54": "getCallWindowSize()", "0x4da74ee6": "setVoteIntention(uint256,bool,bool,string)", "0x6617e11a": "NiceGuyTax()", "0xfe13a823": "computeResponseFirstHalf(uint16)", "0xf7bd2361": "LookAtBalance()", "0xb09bc3bf": "try_to_get()", "0x0cee22e9": "testSetBalanceSetsSupply()", "0xae404996": "oraclize_query(string,string[3],uint256)", "0x2ad95786": "winner(address)", "0xe5fe4f31": "buy(uint8,bytes32,bytes32)", "0xe23941bc": "testDepositWithdraw()", "0xfc89aff6": "submitVerifiedUsers(address[])", "0xddf187b0": "dogFight()", "0xb5f5962a": "CALL_GAS_CEILING(uint256)", "0x92093dd6": "getLastResult()", "0xbfad16f4": "new_offer(uint256,uint256)", "0x01df7f30": "validateProposedThroneConfig(uint256,uint256,uint256,uint256)", "0x4054f5de": "EthVentures3()", "0x244c23ee": "Token(uint256,string,uint8,string)", "0xc3daab96": "withdrawBond(uint256)", "0x5fa21f1f": "enableBetting()", "0x3b591ea7": "AmountToForgeTheNextBlock()", "0x8c3c4b34": "getSaleStatus()", "0x7429f1eb": "multiAccessSetRecipientD(address,address)", "0xf99ff4df": "paged(uint256,uint256)", "0x2a64fb63": "getSaleDate(bytes)", "0x749f9889": "changeAllowedRecipients(address,bool)", "0x053c351b": "oraclize_getPrice(string)", "0x19663f7f": "TransferAmountFromBankAccountToAddress(uint256,address)", "0x5292c1a9": "testThrowsRestartEnforceRevisions()", "0x68f2ab8e": "Currency(string,string)", "0xd6e0bf29": "OwnerDeposit()", "0x94c3fa2e": "getLastBlockHashUsed()", "0x45362978": "query1(string,string)", "0xaff21c65": "getMinimumEndowment(uint256)", "0xe33c7ae2": "scheduleTransaction(uint256,uint256,bytes)", "0x9eb9dd3b": "getBetsProcessed()", "0x3807ba1b": "poi()", "0x7281854d": "GetCategoryValue(uint8)", "0x45ee49b9": "getUltimateOutcomes(bytes)", "0x0109f22e": "CrowdSale()", "0x98596726": "note(uint224)", "0x06900c41": "ZeroPonzi()", "0x3df76482": "fipsPublishData(bytes20,bytes)", "0xe0429b6c": "ShinySquirrels()", "0xa4a7cf5c": "redeemWinnings(bytes32)", "0x2e898ddc": "validateTemporalUnit(uint256)", "0x3af75ee1": "storeBlockWithFee(bytes,int256,bytes,int256)", "0x43e6125d": "Badge(address)", "0x75a6a332": "testThrowRetractNotRetractable()", "0xbed411a0": "CheckPrize(address)", "0x16f3cb5e": "__kill()", "0xe8efc1a0": "updatedValue(bytes32)", "0x9c7264d7": "fillOrder(address,uint256)", "0x9a0af2ec": "getStLength()", "0xf62cce34": "_clearRecordHierarchy(uint256,bytes32[],bytes32)", "0x940f851c": "Ballot(uint8)", "0xd96e5565": "testThrowsRetractNotRetractable()", "0x3a314b24": "SendETH(address)", "0xbd8c1d33": "checkTransferFromToICAPWithReference(address,bytes32,uint256,string)", "0x01da73ff": "isValidChannel(bytes)", "0x8f8bde82": "MicroDAO()", "0x2973e372": "isAlphaUpper(bytes1)", "0x1d2b7155": "activateImportFeeChargeRecord(address)", "0x06ab5923": "setSubnodeOwner(bytes32,bytes32,address)", "0x9d7d6667": "multipliers()", "0x8af49ab7": "maintain(uint256,uint256)", "0x1f3a3a53": "mint(int256,uint256)", "0x74389991": "breakit()", "0x64371977": "set(uint256,string)", "0x3fa6497f": "AdminAddFunds()", "0xba7dc45f": "_removeOperation(bytes32)", "0xf81d087d": "prepareLottery()", "0xd239ea8b": "getSchemasLenght()", "0xa2f3ede2": "computeNameHash(bytes)", "0xa28ecf0b": "sendCryptedHand(bytes)", "0x003b9d88": "setLowerFeePercentage(uint8)", "0x98ea5fca": "depositEther()", "0xb9a0a708": "testChargesAmountApproved()", "0x55291dbd": "claimEther()", "0x2d2800f1": "react()", "0xa9d2293d": "lastClaimBlock()", "0xc45aa04c": "queryShareholders(bytes,uint256)", "0x67af1c81": "getRoundIndex()", "0x50b7b7a2": "setRating(bytes32,uint256)", "0x0aa7881a": "MintableToken(int256,uint256)", "0x0eb3f5a0": "sweepCommission(uint256)", "0x97d47a60": "registerAccountant(bytes,address)", "0xe2c61114": "setImportFee(address,uint256)", "0x6dbe31eb": "testSubBalance()", "0xf5c98aff": "GreeterB(bytes)", "0x79216f5f": "add_monster(uint16,uint16,uint16)", "0x023c23db": "getSize(uint256)", "0x0e1ca8a5": "Oraclize()", "0xa05e822a": "howManyOwners()", "0x313b7b19": "finance()", "0x51a5f2f2": "ConsultingHalf(address,address)", "0x1fb6e99d": "paymentNeeded(uint64)", "0x2bffc7ed": "add(string,address)", "0x5c52c2f5": "resetSpentToday()", "0x39cdde32": "ecverify(bytes32,bytes,address)", "0x64e24f4b": "UpdateClientTokenAccount(address)", "0x0d2560ee": "addMe()", "0xd8589be3": "CoinFlipper()", "0x3b46a7df": "ivote(bool)", "0xa6823189": "parseAddr(string)", "0xd0c24e93": "setNotUpdatable(bytes20)", "0x1f13de92": "inEther(uint256)", "0xb6ce5581": "oraclize_query(string,string[5],uint256)", "0x31ae0019": "KissBTC()", "0xdabf7dc8": "PayoutDividendEarly(uint256,bool)", "0xe3da41b5": "sortWinningNumbers(uint8[5])", "0x8ea98117": "setCoordinator(address)", "0xeff6be2f": "changeBaseFee(uint256)", "0x483a83df": "setKYC(address)", "0xf98a4eca": "executeVote(uint256)", "0x776d1a01": "unvest(uint256,uint256,uint256,uint256,uint256,bool)", "0x1cf52f2b": "isActiveRegistrant(address)", "0x24c9bf5e": "Prizes()", "0xb3822da8": "getContents(uint256[])", "0x999a9965": "setMany(uint256,int256,uint256,bytes,address,bytes)", "0xbade6033": "propose(bytes,uint256)", "0xd38159b8": "testPass()", "0xdabf7ec4": "helper(uint256)", "0xf4993bbd": "executeEmergencyWithdrawal()", "0x46ddb7db": "setAccountBalance(address,uint256)", "0xc12af1ce": "fipsRegister(uint256,bytes)", "0x8f420866": "DEFAULT_SEND_GAS()", "0x62770252": "needsFuneral(uint256)", "0x32921690": "checkDepth(address,uint256)", "0xb3c1a588": "parseMsgData(bytes)", "0x29cd5777": "_tryEraseSingleNode(bytes32)", "0xc6e0c908": "checkTransferFromWithReference(address,address,uint256,string)", "0x9bd99195": "multiAccessChangeOwner(address,address)", "0xa360b26f": "Migrations()", "0x6c6f1d93": "getContractCreationValue()", "0x5445e38c": "_isCycleValid(uint256)", "0x2c85f8e0": "oraclize_query(string,string,string,uint256)", "0xcdda62ad": "FutureBlockCall(address,uint256,uint8,address,bytes4,bytes,uint256,uint256,uint16,uint256,uint256)", "0xc8fdc891": "numberOfMonarchs()", "0xf578fd85": "assertEq0(bytes,bytes)", "0xda311588": "getCoin(uint256)", "0x9a35f886": "__dig_then_proxy(uint256)", "0xfa14df6b": "getChangeRecipientFee()", "0x95f0684b": "getPackageNameHash(uint256)", "0x42909a9e": "create_game()", "0x51582ef3": "sendProxyTransaction(address,uint256,uint256,bytes)", "0xc4e41b22": "getTotalSupply()", "0x110df916": "getChannelID(uint256)", "0x4dc3141b": "CalcAll()", "0xb88a802f": "claimReward()", "0x82b2e257": "getTokenBalance()", "0x9bb0e4df": "getUint(int256,bytes32,string)", "0xc1ae4044": "checkBetColor(uint8)", "0x4a8b5389": "allocateBountyAndEcosystemTokens()", "0xdf06f906": "numBets()", "0xdeb80111": "transfer_asset(address,uint256)", "0x5216aeec": "totalInvested()", "0xe2deaa81": "set_reference(uint256,uint256,uint256)", "0x2ffda1e0": "setBlackflag(uint256,bool)", "0xba45b0b8": "transfer(address,address)", "0x7d7c2a1c": "rebalance()", "0xf32efd3c": "recoverUser(address,address,uint256,uint8,bytes32,bytes32)", "0x4571d4c4": "FutureCall(address,uint256,uint16,address,bytes,bytes,uint256,uint256,uint256)", "0x5fc5d48b": "burnUnsoldCoins(address)", "0x4da47ba0": "TokenSale(address,uint256)", "0x3d9ce89b": "scheduleCall(bytes4,bytes,uint256)", "0x6662e4be": "isWinningBet(uint256)", "0xa501e88d": "Content()", "0x4b70cec4": "getTime(address)", "0x12253a6c": "stopContract()", "0x173825d9": "removeOwner(address)", "0x26121ff0": "f()", "0x7b647652": "LittleEthereumDoubler()", "0x0c5c2ca3": "getIndexName(bytes)", "0x90f2c86d": "convertToWei(uint256,string)", "0x83f95f13": "openClaim(string)", "0xe8b5e51f": "invest()", "0xfdc4b338": "authorizeExtension(uint256,bool,string)", "0x4e116eb8": "unRegisterCertificationDb(address)", "0x5c8a1053": "extend(string)", "0xa932ed0d": "whitelistRemove(address)", "0xa1188e56": "getCurrentDifficulty()", "0xbf1fe420": "setGasPrice(uint256)", "0xead710c4": "greet(string)", "0x144fa6d7": "setToken(address)", "0x42402c2c": "fipsTransferMulti(bytes20[],address)", "0x93e84cd9": "play()", "0x741b3c39": "depositBond()", "0x23306ed6": "getMinimumBond()", "0x5f2e686d": "Ethereum_eight_bagger()", "0x9890220b": "drain()", "0x233120aa": "getChainyURL()", "0xe9dc0614": "vote(bytes)", "0x4df53a0f": "testSetApprovalDb()", "0x5fe27ab0": "createHKG(address)", "0xb56b2627": "add_owner(uint256,address)", "0x6f9a023c": "theultimatepyramid()", "0xc8796572": "collectFees()", "0xea3ebae6": "getConfigBool(bytes32)", "0x213ac932": "addUser(address,uint256,uint8,bytes32,bytes32)", "0xfae9d06d": "calculateTxFee(uint256,address)", "0x45d27edf": "forward_method(bytes,address,uint256,bytes)", "0x8e9ccd04": "computeIndexId(address,bytes)", "0x2f6ae467": "transferDocument(bytes,address)", "0x6e353a1d": "emergencyWithdrawal(address)", "0x7dd56411": "ownerOf(bytes32)", "0x9dc35799": "updateReading(uint256)", "0x246c02e6": "check_depth(uint16)", "0x03d22885": "scheduleCall(address,uint256,bytes4,uint256,uint256,uint8,uint256)", "0x881be8f7": "undo()", "0x953307d8": "revealScissors(string)", "0x7af30442": "testToggleBitFailIndexOOB()", "0x1e0c7ed4": "setConfigBool(bytes32,bool)", "0x2e3be78d": "setPrecisionDirect(uint8)", "0x95a078e8": "hasAccess(address)", "0x245a03ec": "scheduleSetIt(uint256,uint256)", "0xb863bd37": "random(uint256)", "0xa5e62f02": "fallbackRP()", "0x618fa9ce": "getBotBillingIndex(uint256,uint256)", "0x06909f69": "cancel(string,uint256)", "0x2b198366": "addCertifier(address)", "0x57e871e7": "blockNumber()", "0x2b98222e": "getInstitutionByAddress(address)", "0x89eedf00": "setPdfHash(bytes,bytes)", "0x7ac4b05e": "returnMyMoney(uint256)", "0x7fc90182": "Pool(uint256)", "0x291e6777": "sendVote(uint256,uint256)", "0x579badf6": "UniversalFunction(uint8,bytes32,bytes32,bytes32,bytes32,bytes32)", "0xe1f21c67": "approve(address,address,uint256)", "0xa1b9af31": "unlockBets()", "0x92d0d153": "t()", "0x095ea7b3": "approve(address,uint256)", "0x9cb31079": "setLowLimit(uint256)", "0xb971b4e5": "setNotTransferable(bytes20)", "0x2a745971": "BlockKing()", "0x582ca57b": "get_associations()", "0xa0d605c6": "addCertificationDocumentInternal(address,bytes32)", "0x85233869": "NumberOfMiners()", "0xb0349184": "clearRecords(bytes32[])", "0x3e8f5b90": "setConfig(string,uint256)", "0x7c73f846": "getMinimumEndowment(uint256,uint256,uint256)", "0xcc9ae3f6": "getMyReward()", "0xac900c2d": "unregisterSeller(address)", "0x306df22d": "GPSDestination(int256,int256,uint256)", "0x5e968a49": "ownerSetMaxProfitAsPercentOfHouse(uint256)", "0x80dcaf27": "getRefNumber()", "0x4245b0f7": "Lottery()", "0xe46164c5": "waitingForPayout()", "0xa8c3ec48": "oraclize_query(uint256,string,string[2])", "0x58ea80e5": "setThroneCreationPrice(uint256)", "0x8a4fb16a": "getWithdrawal(uint256)", "0x4464aec7": "testTryGet()", "0xf3c37bd5": "Verifier(address,uint256,uint8)", "0xb4c4e005": "testTransferToAcceptAuthority()", "0x346b306a": "oraclize_query(string,string,string)", "0x24032866": "checkExecutionAuthorization(address,uint256)", "0x509f8633": "create_account()", "0x26da8e17": "ownerUpdateCostToCallOraclize(uint256)", "0x74a93e6c": "setTokenHolder(address,address)", "0x1baaeb91": "getSignature(bytes4,uint256)", "0x337c1e28": "getIndexRoot(bytes)", "0xac92fdb5": "getSaleDate(bytes16,uint256)", "0x13b2663b": "cash_received(string)", "0x68e4bd99": "testSetBitSuccess()", "0x20620f37": "onAuctionEnd(string)", "0x85528394": "currentClaimPriceWei()", "0x1995333b": "burnFunds(uint256)", "0x5184ffc9": "setAmbiAddress(address,bytes)", "0x269975d0": "GameDetails(uint256)", "0x29274fe1": "buyBOTx(uint256,string,string,address,uint256)", "0x720c4798": "workshop()", "0x6f9a5eab": "createTx(uint256,address,uint256)", "0xb66a323c": "claimThrone(string)", "0x14cabddb": "joinProof(uint256)", "0x76e4ca0d": "voteQuorum(uint256,bool)", "0xbcb3b5d2": "getGamblesList(uint256)", "0xf2080ba6": "Pong(int8)", "0xab470f05": "getCaller()", "0xe0b1cccb": "updateBalance(address,uint256)", "0x4889ca88": "receiveApproval(address,uint256,address)", "0xbc4b3365": "addFunds(address,uint256)", "0x5d1a3b82": "getOutcome(bytes32)", "0x8204ecdd": "getFee(bytes)", "0x49aa4ee2": "removeVote()", "0x9131d803": "testSetFrontend()", "0x72929b56": "getKudosPerProject(address)", "0x287418e7": "query(uint256,uint16)", "0xbb7859b5": "periodThree()", "0x7dc5cd32": "_patternToNumber(bytes)", "0xf7ea7a3d": "setTotalSupply(uint256)", "0xb3f98adc": "vote(uint8)", "0x655388be": "walkTowardsBlock()", "0x856deacf": "findTag(string)", "0xcaab0acc": "testThrowCreateRetracted()", "0xede8ebf3": "checkApprove(address,uint256)", "0x4296a9cb": "getNodeRightChild(bytes)", "0x03cf4fd6": "expire(uint256,uint256,uint8,bytes32,bytes32,bytes32)", "0x87045369": "setCanCall(address,address,bytes4,bool)", "0x0ed21029": "getIssueAssignee(uint256,bytes32)", "0xc5958bda": "removeFile(bytes)", "0xa8026912": "setSource(address)", "0x05a5b8c6": "verifyTx(bytes,int256,int256[],int256,bytes,int256,int256[],int256)", "0xed3058e0": "transferRight(address,bytes)", "0xba487e62": "newCampaign(uint32,uint96,uint16,uint16)", "0xd6c19fe0": "build(bytes,uint256,uint256,address)", "0xebb741cb": "getChannelSize(uint256)", "0x3b996f40": "quarter(uint32,uint32,uint32,uint32)", "0x0b7373d6": "giveAllBack()", "0x3f887fad": "buyShares(uint256,uint8,uint256,uint256)", "0xb5deeca7": "BaseRegistry()", "0x6dd6e87b": "checkOut(int256)", "0x90a85119": "checkBetResult(uint8)", "0x724121ae": "contentExists(uint256)", "0x67bd69a6": "getLastDuel2()", "0x821e9169": "testFailControllerChargeMoreThanApproved()", "0x083ae1fe": "setPackage(string)", "0x8eaa6ac0": "get(bytes32)", "0xf42aa287": "getBlobStore(bytes12)", "0x32a2c5d0": "getContractAddress()", "0x69d89575": "releaseFunds()", "0x984ac378": "lotteryTitle()", "0x3baf4e1e": "newPayment(uint256,uint256)", "0x938c4307": "scheduleCall(bytes4,bytes,uint16,uint8,uint256,uint256,uint256,uint256,uint256)", "0x47372325": "getChannelSize(address)", "0xcb10e0c5": "getLastDuel1()", "0x1a0919dc": "unregister(bytes32)", "0x6a3c1198": "_projectCancelNew()", "0x976b01c0": "setNotRetractable(bytes20)", "0x44d03ac6": "BlockhashFetch(address)", "0xb0fd935b": "registerCertificationDb(address)", "0x2c7c4549": "PurchasableToken(uint256)", "0x3e0a51b4": "TweetAccount()", "0xc5f310c0": "register(bytes12)", "0xc10dd4c6": "getEvents(bytes32[],address)", "0x37a6b9f8": "recordCallback(address,uint256,bytes,bytes)", "0x1b9265b8": "pay()", "0xc91d7e9c": "getFee(bytes32[])", "0xf6c5c80d": "cleanUp()", "0x590e1ae3": "refund()", "0x9c709343": "split(bool,address)", "0xe68d3ae3": "escrow(uint256,string,address,uint256)", "0xa1cb31b7": "_state()", "0xfbac3951": "isBlocked(address)", "0x29dfdded": "addNewDonkey(address)", "0x7bc49a95": "play(uint256,uint256)", "0x5329c681": "checkTimeout(uint256)", "0x7c05caf3": "testCreateCostAuth()", "0x8606f905": "balanceOf(address,bytes)", "0xbe45fd62": "transfer(address,uint256,bytes)", "0xd7130651": "getCity(uint256)", "0xb2353d69": "updateRightLottery(address)", "0x01bb85a4": "__startBlock(string)", "0x4316abbb": "newJester(address)", "0x5c3e426c": "adminRetrieveDonations(address)", "0x293ffca3": "AddressReg()", "0x4156fdb7": "createSwap(uint256)", "0x2852b71c": "accept()", "0x027a5e3f": "getLastVersion(bytes)", "0x3d90d44d": "addPowerSource(address,uint256,uint256)", "0x1ab06ee5": "set(uint256,uint256)", "0x50f07cf9": "setReadingDelay(uint256)", "0x669e48aa": "get(uint256,uint256)", "0xdea9c72b": "getLatestPreReleaseTree(bytes32,uint32,uint32,uint32)", "0x60f66701": "useCoupon(string)", "0x2431f164": "process_payment()", "0x17623e5b": "unauthorizeManager(address)", "0x367bbd78": "strlen(string)", "0xbab86ea8": "test(string,string)", "0x25f3da52": "GetBankAccountNumber()", "0xebb045fa": "PublicResolver(address)", "0xdd57d5c5": "setTrust(address)", "0x22e803c2": "transferBounty()", "0x12c8052f": "won()", "0x3535cd52": "setDailyCosts(uint256)", "0x92d8c8cf": "setupImportFee(address,uint256)", "0x53caf582": "testThrowSetNotUpdatableNotOwner()", "0xb3ade772": "shipProducts(string,string)", "0x61472fd4": "CSGOBets()", "0x5c7c9aa4": "checkAccountState(address)", "0x6560a307": "suggestedGas()", "0xd0d552dd": "setAsset(address)", "0xc02f081a": "shiftBits(bytes,int256)", "0x9348cef7": "reveal(uint256,uint256)", "0x5ca1c5a0": "getNodeValue(bytes)", "0x34c1b4ba": "sha(bytes)", "0xe0117441": "setRegistrationPrice(uint256)", "0x92698814": "reserved(bytes32)", "0xaa1e84de": "hash(bytes)", "0x2125b65b": "transfer(uint32,address,uint224)", "0x9f9eac67": "ChangeName(string)", "0x7eb69ba1": "hint(int256,bytes32,string,bytes20)", "0xc6888fa1": "multiply(uint256)", "0xa9059cbb": "transfer(address,uint256)", "0x84c344fe": "_register(bytes4,string)", "0x744d8b4f": "recordWin(uint256,uint256)", "0xad1ef61e": "donkeyInvested(address)", "0xb4787dc5": "linkEID(bytes,bytes)", "0x7b1cbb13": "getChannelValue(bytes)", "0xd0febe4c": "buyTokens()", "0x74f519db": "setLastTimestamp(uint256,uint256)", "0xe30081a0": "setAddress(address)", "0x6fd902e1": "getCurrentBlockNumber()", "0x25d8dcf2": "betAndFlip()", "0xc062f578": "updateStage()", "0xe0cfc05c": "testThrowsRetractLatestRevisionDoesntHaveAdditionalRevisions()", "0x854f4817": "buyKissBTCWithCallback(address,uint256)", "0x58b1f29c": "refundBounty(uint256)", "0x0645b5d5": "getMyShareholderID()", "0xe9c63b9c": "requestPeerBalance()", "0x1b03316f": "getSecond()", "0xbcc6092a": "MyEtherBank()", "0x1cf43b63": "extractExportFeeChargeLength()", "0x5e58f141": "shares(address,bytes,int256)", "0x6103d70b": "withdrawPayments()", "0x3e0663e0": "AdminDrawProcess()", "0xa0f029fc": "ContractorInterface(address,address,address)", "0xe604cf9f": "get_all_squares()", "0xd13d1ace": "scheduleCall(bytes,bytes,uint16,uint8,uint256,uint256,uint256,uint256,uint256)", "0xb74bc710": "LuckyDoubler()", "0x611f69de": "__proxy_motion(address,uint256,uint256,bytes)", "0xbb814e9e": "versionExists(bytes32)", "0x545e7c61": "deploy(address,address)", "0x1b437d0c": "compareLastCalldata(bytes)", "0x845051d3": "testContractsNotNull()", "0x23de6651": "emitTransfer(address,address,uint256)", "0xd78c20ff": "voteApprove(uint256)", "0x6f13e01b": "EthVenturePlugin()", "0x1f6b0a9d": "getReleaseLockfileURI(string,uint32,uint32,uint32,string,string)", "0x13d4bc24": "buyTokenProxy(address)", "0xd509b16c": "testWithdraw()", "0x2f54bf6e": "isOwner(address)", "0xf60381a1": "stra2cbor(string[])", "0x34dbe44d": "getLastBlockNumberUsed()", "0x28dcfdac": "getSignsCount(uint256)", "0x2888f9d0": "updateMaxBet()", "0xc3b2556d": "lookup(bytes)", "0x6fbaaa1e": "currentMultiplier()", "0xe241c1d9": "deriveKey(uint256,uint256,uint256)", "0x5b37e150": "create(bytes32,bytes)", "0xa1c95ac2": "GSIToken(uint256,string,uint8,string,address)", "0xfc9e53df": "setNextRegistrar(address)", "0x34e8980f": "bootUpHangouts()", "0x457dd8b3": "setMasterKey(address)", "0xdb833e3a": "sellShares(bytes32,uint8,uint256,uint256)", "0x1e5330ca": "checkBetResult(uint8,address,bytes32,bytes32)", "0x4a00a522": "homebase(int256,int256)", "0xb938bf42": "sendBounty(bytes32)", "0x46be96c3": "amountFilled(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32)", "0x2fa00e58": "fipsTransfer(bytes20,address)", "0x95669952": "debtor(address,uint256)", "0x3f19d043": "getContributions(address)", "0xeb455dc6": "sendBitcoin(string,uint256)", "0x034cb28e": "addressOf(address,bytes)", "0x1e26fd33": "setBool(bool)", "0x1b55ba3a": "Start()", "0x6f3fe404": "updateBalances()", "0xc45b415e": "createRequest(address[4],address,uint256[11],uint256,bytes)", "0x180aadb7": "underLimit(uint256)", "0xb44bd51d": "getConfig(string)", "0x93d79105": "hashRelease(bytes32,bytes32)", "0x9f8a13d7": "isActive(address)", "0x257bcd6a": "placeBet(uint256,bytes32,bytes32)", "0xdd10d97e": "getPlayerWaiting()", "0x6d16f79c": "__transferWithReference(address,uint256,string)", "0x6ce1417e": "Fund()", "0x67beaccb": "scheduleCall(bytes)", "0x4b0697e4": "Manager(address)", "0x6a7fc8b7": "setDailyWithdrawLimit(uint128)", "0x7a6e9df7": "getTimestamp(bytes)", "0x797af627": "confirm(bytes32)", "0x81183633": "setStandard(bytes32)", "0x6a1db1bf": "changeFee(uint256)", "0x4e6ab570": "insert_order(address,bool,uint32,uint128)", "0xa4e2d634": "isLocked()", "0x7c79ebce": "expired(uint64)", "0x20965255": "getValue()", "0xfd408767": "fireEventLog4()", "0xcf6b3822": "WatchCollectedFeesInSzabo()", "0x8f7fe231": "ValidetherOracle()", "0xbf12165e": "fillUpSlot(uint256,uint256)", "0xdabdc1f2": "ChangeActiveDigger(address)", "0xe9e7a667": "get_stake(bytes32)", "0x0ad95b44": "bribery()", "0xdb6fcf01": "is_destroyed(uint256)", "0x4378a6e3": "getAttributes(uint256)", "0x71589d6b": "newponzi()", "0x47274dbe": "disableUser(address,address)", "0xb40a5627": "bidCount()", "0xf1eae25c": "mortal()", "0x13af4035": "setOwner(address)", "0xaf030d2c": "setResult(uint256,uint256,bytes32)", "0x098ab6a1": "snapshotCount()", "0x27cca148": "lastClaimedBlock()", "0x940c154b": "lockBet(uint256)", "0x378c0605": "buyTickets(address)", "0xbcfcb03e": "allocateFounderTokens()", "0x0138e31b": "_jAdd(uint256,uint256,uint256,uint256)", "0x0a7f4239": "getAccountFundContract(address)", "0xc96593a0": "The10ETHPyramid()", "0x22beb9b9": "scheduleDoIt(uint256)", "0xdb0e127a": "openDoor()", "0x3dc02266": "fipsRegister(uint256)", "0x7d242ae5": "setBasePrice(uint256,bytes)", "0xe82b7cb2": "proxySetCosignerAddress(address,bytes32)", "0xa60bbcd3": "ModelCoordinator()", "0xc26aa3c9": "lockUnicorn(uint256)", "0x96ff7e97": "requestIdentity()", "0x99753de7": "clear_level()", "0x69d79ad5": "moneySumAtSettlement(address,uint256,uint256,int256,uint256,uint256)", "0xda359dc8": "setBytes(bytes)", "0x6edb4cf6": "testThrowRetractLatestRevisionDoesntHaveAdditionalRevisions()", "0x9d170c5d": "getRef(string)", "0x11cd98ed": "convertToAllTable(uint256,string)", "0x67f809e9": "DynamicPyramid()", "0xd5f37f95": "sign(uint256,uint256,address)", "0xf5562753": "getClaimAmountForBlock(uint256)", "0xc9bbc8c0": "donkeyName(address)", "0x5858ef10": "testErrorNonOwnerCantBreach()", "0x74388347": "checkBetDozen(uint8,address,bytes32,bytes32)", "0xee564544": "_slotCancelNew()", "0xf3bb9741": "commitmentCampaign(uint256,bytes32)", "0x2b68b9c6": "destruct()", "0xa9b8f7b8": "ProtectTheCastle()", "0x16181bb7": "shortSellShares(bytes32,uint8,uint256,uint256)", "0xb524abcf": "totalSupply(bytes32)", "0x8006745b": "getPayout(address)", "0x137c638b": "getExtraGas()", "0x824d5603": "getIndex(uint16,uint16)", "0x245a6f74": "isProxyLegit(address)", "0x9eded57a": "paybackLast()", "0x7b1aa45f": "ownerDeposit()", "0x974654f4": "requiredGas()", "0x76d690bb": "BountyList()", "0xf4b2dfea": "Matching_Finneys()", "0xbd66528a": "claim(bytes32)", "0x85eac05f": "changeOwnerAddress(address)", "0xa69df4b5": "unlock()", "0xe6d9bb0f": "secondsUntilEnd()", "0xcd57a448": "SwapContract(address,uint256)", "0xb245fc92": "findNextMonth(uint256,bytes)", "0x7620f4bb": "fipsNotaryLegacy68b4()", "0x61886014": "combineDice(uint8,uint8)", "0xdf4ec249": "step3()", "0x2262cd94": "wroom()", "0x1099d3ec": "scheduleTransaction(uint256,uint256,uint256,bytes)", "0xd8c34127": "isKnownSignature(string)", "0x8afa08bd": "setDrawDate(uint256)", "0xdb18c972": "play4(address,uint256)", "0x2f30283e": "testSomething()", "0x8ca17995": "divest(uint256)", "0x1ef3755d": "restart()", "0x99bb875c": "funeralAndBirth(bytes,int256,bytes)", "0x157ad5a1": "canWithdrawBond(address,uint256)", "0xfd8055d2": "updateBOTBillingInfo(uint256,string,address,string,string,uint256)", "0xa10889fa": "setVersion(uint32,uint32,uint32,string,string)", "0x51cff8d9": "withdraw(address)", "0xbe999705": "addFunds(uint256)", "0x2e5d1042": "requestPayout(uint256,uint256,bytes32,uint256,uint256)", "0xb50954b6": "cancelWaitingForOpponent()", "0xc42cd8cf": "etherSplit(address,address)", "0x42ce1488": "upload(string)", "0xad04592e": "owner_deposit()", "0xc2cf7326": "hasConfirmed(bytes32,address)", "0x1dbf3bc7": "spend(uint256)", "0x36b81feb": "Deed(address)", "0xf47289e1": "_ecDouble(uint256,uint256,uint256)", "0x026993e0": "Midas(address,address)", "0x5e404de3": "setMaximumCredit(uint256)", "0x0194db8e": "sum(uint256[])", "0xa04a0908": "execute(address,bytes,uint256)", "0x2b4a3b31": "doTransferFrom(address,address,uint256)", "0x96ed10a4": "issuePOIs()", "0xb75c7dc6": "revoke(bytes32)", "0x6056969b": "announce(bytes32)", "0xd63547e6": "GetFreeCnt()", "0x788e26e7": "sponsorDeposit()", "0x550dd006": "calcCostsBuying(uint256,uint8,uint8,uint256)", "0xd4b1d19f": "testThrowsTransferDisabled()", "0x04706fdf": "giveContributionsBackProfitBugged()", "0x5d5483b3": "WatchAppliedFeePercentage()", "0x6bf8f85a": "forceFinish()", "0x3edd90e7": "NewOwner(address)", "0x7c69b5d1": "NewDeposit(uint256)", "0x866f6736": "trustedChildWithdraw()", "0xcdcb7c8f": "chase()", "0x60dccd89": "getContentAccount(uint256)", "0xbff1f9e1": "totalUsers()", "0x1aca00fd": "variable(uint256)", "0x6e658fbe": "myFundsExpireIn(uint256)", "0xddb1bdc8": "credit(address,uint256,uint256)", "0x934bc29d": "exampleFunction(uint256)", "0x113e6b66": "fipsAddToLedger(bytes20,address)", "0x1a88bc66": "slot()", "0xec97cff7": "addCertificationDocument(address,bytes32)", "0x0790e880": "setBlockappsAddr(address)", "0xe0ad411d": "assets(bytes)", "0x791b51f1": "Consulting(address,address)", "0xa26dbf26": "totalParticipants()", "0xb78b52df": "allocate(address,uint256)", "0xdb29fe12": "addShareholder(address)", "0x06459119": "testThrowsTransferNotTransferable()", "0xbadbaa3c": "setCallData()", "0x2c6b2c92": "checkProfitLossSinceInvestorChange()", "0x8aa6f1b1": "setUltimateOutcome(bytes32)", "0xecb98714": "random_damage(uint256)", "0x506e106c": "setToS(string)", "0xf0d474f9": "underdogCount()", "0x2212dbc3": "get_timestamp()", "0xd504ea1d": "getArray()", "0x9b29cb23": "getDailyPayment()", "0x9d3e069c": "StartDraw()", "0x12494160": "isHolder()", "0xbbd4e8c9": "numDeposits()", "0xfea2920e": "createNewDraw()", "0xff556ecb": "releaseUnicorn(uint256)", "0x3bed33ce": "withdrawEther(uint256)", "0xaa9669c1": "roll(uint256,bytes)", "0xa00aede9": "scheduleCall(uint256,address)", "0xc0819961": "Invest()", "0x1ed24195": "getPeriod()", "0x5babb758": "testSetUp()", "0xaaf9d13e": "buyTopDog(uint256,uint256)", "0x7c45ef6c": "stringToSig(string,string)", "0x7353f62b": "testGetApprovalDb()", "0xef7507c8": "testWinner(uint256)", "0x7ef95c6f": "extractAccountAllowanceRecordLength(address)", "0x66099706": "getChannelCred(address,uint256)", "0x5c242c59": "query1(uint256,string,string,uint256)", "0x299a7bcc": "setOwner(address,address)", "0xe1152343": "payout(uint256)", "0xd40a71fb": "step1()", "0xda9c6a46": "getReplyCount(uint256)", "0xffb7bfba": "watchProposal(uint256)", "0x2e1a7d4d": "withdraw(uint256)", "0x03da8902": "transfearDBOwner(address)", "0xc9bd2893": "fines()", "0xfdd3a879": "quick()", "0xda0774ad": "getCallFeeScalar(uint256,uint256)", "0x0f2c9329": "split(address,address)", "0xa3912ec8": "receiveEther()", "0xfd6f5430": "setContent(string,bytes32)", "0x99e0021f": "mergencyCall()", "0xb7aec6a5": "scheduleCall(address,bytes,uint256,uint256,uint8,uint256)", "0x23145ca0": "forceCheck()", "0xc7cf28fe": "canClaimTimeout()", "0x26db7648": "proposedVersion()", "0x60a60fd8": "testProxyCallWithValue()", "0x044d0b06": "oraclize_query(string,string[2])", "0x4f013184": "investInTheSystem()", "0x0c9fd581": "assertTrue(bool)", "0x09574810": "getOperationsNumber()", "0x6e2edf30": "ETCSurvey(address)", "0x3cc86b80": "GetMoney(uint256,address)", "0xf7b89a3e": "getTotalCosts()", "0xb18c6847": "manualUpdateBalances()", "0x8a65d874": "userStats(address)", "0xf80b3cfa": "checkBetLowhigh(uint8)", "0xc2def3b9": "getOrganizer()", "0x2dae9878": "BankOwner_EnableConnectBankAccountToNewOwnerAddress()", "0x1998aeef": "bid()", "0xc64e8bc0": "executeN(uint256)", "0xd4088e33": "setPrice(uint256,uint256,uint64)", "0xd263b7eb": "ownerkill()", "0xc478fc37": "EtherWheel(uint256,uint256,uint8)", "0x05b765ea": "getCertifierStatus(address)", "0x93c32e06": "changeFounder(address)", "0xf207564e": "register(uint256)", "0xae6c0b03": "canWithdrawBond(uint256)", "0x2b1071c9": "testTransferToNullAuthority()", "0xd9feeeb6": "fillMyOrder(uint256)", "0x9fb755d7": "setHotWallet(address)", "0x7f98444f": "randomEnd()", "0xf3fef3a3": "withdraw(address,uint256)", "0x48a0d754": "available()", "0x3af94817": "getPongvalRemote()", "0xec21a913": "setUint256(int256,uint256)", "0x6099af40": "setConfigBool(bytes,bool)", "0xf0cbe059": "proxyTransferFromWithReference(address,address,uint256,bytes32,string)", "0xf93589ce": "didWin(bytes)", "0x1eb5ea2e": "returnFunds()", "0xa6027d53": "IconomiTokenTest(uint256,string,uint8,string,uint256)", "0x8a323b38": "Contract(uint256,string,uint8,string)", "0xb0f07e44": "registerData()", "0xc9d27afe": "vote(uint256,bool)", "0x64265b1a": "share_transfered(string)", "0x78205f67": "testThrowTransferEnableNotTransferable()", "0x081780f4": "clearRecord(bytes32)", "0xdd137b5d": "toBase58(uint256,uint8)", "0x9483e91a": "withdraw(address,uint256,bytes,uint256)", "0xc6502da8": "basePayment()", "0xe17e1274": "testTransferToRejectAuthority()", "0x9af605cb": "__proxy(address,bytes,uint256)", "0x7a8df1b9": "getAffiliateInfo(address)", "0x46b305d6": "lockBetsForWithdraw()", "0x7d4cf602": "buildDSBalanceDB()", "0xce87f626": "replaceWizardRP(address)", "0x125b8f06": "isInNextGeneration()", "0xd0068f80": "getClient(uint256)", "0x7f0899f2": "AddTicket(bytes5[])", "0xb15dcc25": "query(address,bytes2,uint256)", "0x07a9574a": "changeLeaderMessage(string)", "0x16e55626": "getDogName(address)", "0xbc058968": "updateThingData(bytes32[],bytes32[],uint88)", "0x02aa274b": "setForward(bytes4,address)", "0x08f235ec": "getDefaultPayment()", "0x1dd4914b": "withdrawEtherOrThrow(uint256)", "0x7ca31724": "tokenId(address)", "0x0c4f65bd": "getOwnerAddress()", "0xeec3cb41": "placeBet(bool[],uint256,uint256)", "0x9054bdec": "toTimestamp(uint16,uint8,uint8,uint8,uint8,uint8)", "0x468f02d2": "getUnderlyingPrice()", "0x74331be7": "sete(address)", "0xb05e390a": "TokenEther(string,string)", "0x89cc5ea8": "bid(string,address,uint256)", "0xa8893a6e": "getNumOfSalesWithSameId(bytes16)", "0x3defb962": "heartbeat()", "0x15a03930": "TossMyCoin()", "0x1d8ae626": "Security(string,string)", "0xf1bca7a4": "doCall(uint256)", "0xae6215d8": "getBlockHeight(bytes)", "0x8124bb0f": "continueExecution()", "0xc1cbbca7": "contribute(uint256)", "0xa48566ba": "serverSeed(address,bytes)", "0xc0f5a9cb": "deleteThing(bytes32[])", "0x4136aa35": "isAlive()", "0x6fe665e9": "SlotMachine()", "0xfaff50a8": "rootNode()", "0xaf769eff": "Paper()", "0x77863b61": "CrossWhitehatWithdraw(uint256,address)", "0x2bed55b0": "buildDSEasyMultisig(uint256,uint256,uint256)", "0xd2ef7398": "challenge()", "0x96e4ee3d": "convert(uint256,uint256)", "0x2dff6941": "content(bytes32)", "0x4d536f9f": "validateNameExt(bytes)", "0xd4d5d32a": "collectFee()", "0x6620a935": "sendToOwner()", "0x5084da18": "fipsOwner(bytes20)", "0xe419f189": "multiAccessIsOwner(address)", "0xa9fbc614": "lookupTicketHolder(uint256)", "0x11f72496": "testT()", "0x7365870b": "bet(uint256)", "0x09861b81": "flooredSub(uint256,uint256)", "0xe28fed1e": "userRescues(address)", "0x28cc413a": "getProof(uint256,uint256,uint256)", "0x9a8f09bd": "newKing(address)", "0x5d068051": "sendFees(address)", "0x49cbe338": "tryRead(uint64)", "0x691bfc89": "goods(uint16,uint256)", "0xfc36e15b": "vote(string)", "0x48107843": "getNextCallSibling(address)", "0x6461fe39": "transferFromWithReference(address,address,uint256,string)", "0x804e11dc": "testThrowsDisownNotTransferable()", "0x76f30ca1": "toContentID(address,uint256,string,bytes)", "0xa77b2e37": "Coin()", "0x3f2f1596": "setupTreasury(address,uint256)", "0x3de9e4c6": "__transferFromWithReference(address,address,uint256,string)", "0x612e45a3": "newProposal(address,uint256,string,bytes,uint256,bool)", "0x4cdb48e4": "isValidNym(address)", "0x5afa5036": "isCertified(address)", "0x9a1b420b": "OraclizeAddrResolver()", "0xec5c9036": "Crowdsale(address,uint256,uint256)", "0x01095962": "oraclize_setCustomGasPrice(uint256)", "0xe7e2aa0e": "buyer_cancel()", "0x2da8f764": "submitVideo(string,string)", "0x3395dc70": "acceptTransfer(address,address,uint256)", "0xdf143fb7": "HackerGold(address)", "0x63334c58": "transferETC(address)", "0x6a5da6e5": "followCampaign(uint256)", "0x49fb2dc5": "add_to_association(uint256,uint256,uint256)", "0x953aa435": "GetPrice(uint8)", "0xc1257bad": "testPassingAProposal()", "0x3bc5de30": "getData()", "0x4abb9d39": "depletable()", "0x129484b6": "changeFeeRecipient(int256,int256,int256,int256,int256,int256)", "0x902e64e5": "Oath()", "0x8c4dd5cd": "Democracy()", "0xbea124a6": "query(bytes,bytes,int256)", "0x69347990": "ownerWithdrawl()", "0x606deecd": "requestData()", "0x6720ceb1": "sendPayment()", "0xb1050da5": "newProposal(address,uint256,string,bytes)", "0xfeaa29d8": "insertProfitHere()", "0x36f9f49c": "etherandomSeed()", "0xae815843": "query(uint256,string,string,uint256)", "0x752d349c": "depthCheck(int256,int256)", "0xa24835d1": "destroy(address,uint256)", "0x26070774": "Token(address)", "0x6e0d98fe": "setProbabilities(uint32[])", "0x0761a004": "step(uint256,bytes)", "0xad82dcac": "testBlockhashCorrectFee()", "0x0900f010": "upgrade(address)", "0xa288fb1f": "setConfigUint(int256,bytes,uint256)", "0xe88b8ac6": "confirmAndCheck(bytes)", "0xf1a00a53": "unregisterListening(address)", "0xb17acdcd": "collectFees(uint256)", "0xb5d0f16e": "getGasScalar(uint256,uint256)", "0xae999ece": "reserve(string)", "0x95ceb4b3": "winningProtocal()", "0x1a93fa4b": "reorganizeSubUsers()", "0x9243e088": "setEnforceRevisions(bytes20)", "0x342454c7": "isDigit(bytes1)", "0x8e2c6f4d": "initiateVerification(address,bytes,bytes)", "0xddb5b3ac": "SellTokens()", "0xd18dfdc9": "parrot(uint256)", "0xd3732642": "FastRealisticPyramid()", "0x37ab8f20": "notifyPlayer(uint256,uint256,uint256,uint256)", "0xfb6e155f": "availableVolume(address,uint256,address,uint256,uint256,uint256,address,uint8,bytes32,bytes32)", "0x50ab6f7f": "getMsgs()", "0x6b6a53fa": "testThrowsRestartNotOwner()", "0x4dc7cc55": "terminateAlt()", "0x8b9726c1": "multiAccessCallD(address,uint256,bytes,address)", "0x5a74dee5": "multiAccessRemoveOwnerD(address,address)", "0xfd339d18": "testAuthorityTryAuthUnauthorized()", "0xd3d6a975": "testThrowsTransferNotEnabled()", "0x8aa001fc": "getSecond(uint256)", "0x2ec2c246": "unregister(address)", "0xbbdb31cb": "challenge(uint256,address,bool)", "0x635cfda2": "Incrementer()", "0x704b6c02": "setAdmin(address)", "0xc1c723f4": "validateProposedMonarchName(bytes)", "0xadf5e565": "verify(bytes,address,uint256,uint8,bytes,bytes)", "0xbe7cddf8": "TwoD()", "0xc71b583b": "closeRequest()", "0x9cbf9e36": "createToken()", "0x69c4113d": "setNewBudget(uint256,uint256,uint256,uint256)", "0xd4649fde": "expire(uint256,uint8,bytes32,bytes32,bytes32)", "0xf9a7a2ef": "named(bytes)", "0xdbf45aa3": "EthBank()", "0xbb3ce7fe": "DepositHolder()", "0xfb5d5729": "getPongvalTransactional()", "0x2f0b15f6": "testGetUnset()", "0x62891b5d": "multiAccessChangeRequirement(uint256)", "0x538e0759": "refill()", "0x24c65f35": "updateRefundGas()", "0x62b24189": "DepositToBankAccountFromDifferentAddress(uint32)", "0xd81f53fd": "EtherId()", "0xcea943ee": "getSaleConfig()", "0x23647398": "testThrowRetractNotOwner()", "0xcdcd77c0": "baz(uint32,bool)", "0xa677fbd9": "example2Func()", "0xa02b9aac": "getPaymentDataByAddress(address)", "0x268eb055": "setDescription(uint64,bytes)", "0x09d2d0b9": "setServiceAccount(address,bool)", "0x5f70d9ac": "getBot(uint256)", "0x63f80de3": "issueCoin(address,uint256,uint256)", "0x8570153e": "publish(string,string,bytes,address[])", "0x7975c56e": "oraclize_query(uint256,string,string)", "0x7c3064f1": "refundStake()", "0xef4ffee2": "Honestgamble()", "0xfdc193a4": "test3Fails()", "0x0eb495c2": "pushCity()", "0x6a357465": "payHours(address,uint256)", "0x93f0bb51": "order(address,uint256,address,uint256,uint256,uint256,uint8,bytes32,bytes32)", "0x0a9254e4": "setUp()", "0xc490a266": "toUInt(bytes)", "0xf666323e": "UUIDProvider()", "0x857d4c07": "throwScraps(uint256)", "0x7fd8ee68": "computeNameHashExt(bytes)", "0x7bd703e8": "getBalanceInEth(address)", "0x68f65f02": "ChangeShownDenomination(bool,bool,bool,bool)", "0xc7f758a8": "getProposal(uint256)", "0x824dbc9a": "changeMembership(address,uint256,bool,string)", "0x2a228fc2": "processWithdrawals()", "0xe0457884": "betResolution(uint8,uint8,uint8,bool)", "0x550538f6": "getOneTimeCosts()", "0xf3e84cf3": "createNewRevision(bytes32,bytes)", "0x77fe38a4": "transferToICAPWithReference(bytes32,uint256,string)", "0xcc189d00": "Vault(address,uint256)", "0x9e281a98": "withdrawToken(address,uint256)", "0x7b12df39": "userProfits()", "0xa843c97f": "attack(uint256,uint256,uint256[])", "0x9da680f3": "adjustRegistrationFee(uint256)", "0x3ea3f6c5": "activateRegistrar()", "0xb36df681": "ExecutableBase()", "0x0f3a1412": "getArrlist(uint256,uint256)", "0x8b7f0ddd": "register(address,address,string,string,bytes32[],uint256,string)", "0xb5d3a379": "CanaryTestnet()", "0x9894221a": "SendCashForHardwareReturn()", "0xc2b12a73": "setBytes32(bytes32)", "0xff1b4341": "easyPropose(address,uint256,uint256)", "0x927ed13a": "newClient(uint256,address)", "0x9b9d0364": "_setFeeStructure(uint256,uint256,uint256)", "0x01518d76": "sendQuery(uint256)", "0x4112b7f1": "tryGetNameOwner(bytes)", "0xb759f954": "approve(uint256)", "0x810a882f": "setConfigBytes(bytes32,bytes32)", "0xea7a7184": "testGetBalanceDb()", "0xb0c8f9dc": "add(string)", "0xe59f611f": "InputLimit(uint256)", "0xdce293a7": "minLength(uint256)", "0xf509b627": "confirm(address,uint224,uint32,address)", "0xd48bfca7": "addToken(address)", "0x044f9ac8": "findThroneCalled(bytes)", "0x1d57bcf7": "ProofLibInterface()", "0x75830463": "checkBetLowhigh(uint8,address,bytes32,bytes32)", "0x2a45a39a": "Post(address)", "0x29cbdc86": "buyin(address,uint256)", "0x3cbfed74": "getBondBalance()", "0x80a23ddf": "mintBadge(int256,address,uint256)", "0x96b76c23": "stand(uint256)", "0xc392f5a0": "getAllPackageReleaseHashes(string)", "0x4847a79c": "_transfer(address,uint256)", "0x905e6e42": "JSON_Test()", "0x9f489e4e": "getDeposit(uint256,address)", "0x4f8e624e": "Greeter(string)", "0x96013c9c": "testLatestPkgGetter()", "0xe4fc6b6d": "distribute()", "0x423d4ef2": "createChannel()", "0x24d7806c": "isAdmin(address)", "0x691fb8ea": "jumpIn()", "0xd50f6bf0": "transferETH(address)", "0xd0e0813a": "promote(address)", "0x528eedcb": "sendSafe(address,address,uint256)", "0x00faf4dd": "getTokenDivisor()", "0x46a1d95f": "closeMarket(bytes)", "0x318a3fee": "relayTx(bytes,int256,int256[],int256,int256)", "0x49d55d9d": "receiveTransfer(uint256)", "0x4fa99dd0": "Matching_Ethers()", "0x99a5d747": "calculateFee(uint256)", "0x3c67c51e": "testLogs()", "0x12ab7242": "setupStackDepthLib(address)", "0xad9ec17e": "setGreyToken()", "0xc37e8cb2": "testExportAuthorized()", "0x43046844": "placeBet(uint8)", "0xc6e1c178": "TheLuckyOne(bytes)", "0x13d1aa2e": "f(uint256,uint256)", "0x64a4a5d7": "testBitsEqualSuccess()", "0xfb1669ca": "setBalance(uint256)", "0x40fdef80": "administration(uint256,string,uint256,uint256,address)", "0xcf7315c6": "retract(bytes20)", "0x76196c88": "setDnsrr(bytes32,bytes)", "0x08bf2d0d": "getOrderBook(uint256,uint256)", "0x021c309a": "solveBet(address,uint8,bool,uint8)", "0x4de162e4": "extractAccountLength()", "0x56fa47f0": "split(address)", "0xb3a0b1ef": "basicInfoGetter()", "0x26066ad5": "offer(uint256,bytes,uint256,bytes)", "0x99c724ef": "skipInLine(uint256,uint256)", "0x838445e8": "EtherAds(address,address,address)", "0xe06174e4": "settings()", "0xfac5bb92": "getPreRelease(bytes32)", "0x93c94acb": "calculateRewards(uint256[3][3])", "0xd7fa1007": "setHash(bytes32,bytes32)", "0x2a714078": "triggerAuth()", "0x4cd995da": "registerCompany(address,string)", "0xf6469342": "_setPackedBlockNumber(bytes32,uint256)", "0x8e7fd292": "trySetSubnodeOwner(bytes32,address)", "0x4f573cb2": "withdrawRevenue()", "0x924c28c1": "ContractInterface(address,address,address)", "0x4fc9c91a": "identityOf(bytes32)", "0x19901f1d": "TokenSale(uint256,uint256)", "0xaf8b7525": "CollectAndReduceFees(uint256)", "0x3ccb7dc9": "CrowdFund(uint256,uint256)", "0xaeeb96af": "Highlander()", "0xa126c5df": "GAS_TO_AUTHORIZE_EXECUTION()", "0x13c89a8f": "getAllowedTime(bytes32)", "0xf38b0600": "fireEventLog3()", "0xc7144269": "changeSettings_only_Dev(uint256,uint256,uint256,uint256,uint16,uint256,uint256,uint256,uint8,uint8)", "0xefc81a8c": "create()", "0x7429c086": "repeat()", "0x9c0a4bbc": "AlwaysFail()", "0xc3d23e10": "checkBet()", "0x28a45038": "testTryProxyCall()", "0xa668d7c9": "NiceGuyPonzi()", "0x06fe1fd7": "getPackageName(bytes32)", "0x29f27577": "InvestorList(uint256)", "0x57e25a79": "PullPaymentCapable()", "0x6d1669e1": "approveAndCall(address,address,uint256,bytes)", "0xa7e93e87": "retractLatestRevision(bytes20)", "0x9c7e8a03": "addParticipant(address,address,uint256)", "0xc6ab4514": "sendRobust(address,uint256,uint256)", "0xe8f6bc2e": "changeAccountLevelsAddr(address)", "0xb5d1990d": "numRecords()", "0x3e853128": "getGasForXau(address)", "0xa1add510": "hasRelation(bytes32,bytes32,address)", "0x31e3e2fe": "WithDraw()", "0x86723215": "createMarket(bytes,uint256,uint256,address)", "0xce845d1d": "currentBalance()", "0xc3a2c0c3": "scheduleCall()", "0xcf8eeb7e": "subBalance(address,uint256)", "0xaeb4f0d3": "RegisterTwo(address,address)", "0x3c716e08": "updateAuthority(address)", "0x9919b1cc": "getContentsByRanks(address,uint256,uint256,uint256)", "0x0f06670a": "didWin(bytes32)", "0x74e4435f": "getUserAddress(uint256,bytes32)", "0x4664b235": "bytes32_to_bytes(bytes,bytes,bytes)", "0x2ac9bf09": "bid(uint256,uint256,uint256)", "0xf11c4482": "approveFromProxy(address,address,uint256)", "0xfe992c98": "balanceOfAll(address)", "0x43e332c5": "Last_block_number_and_blockhash_used()", "0x0066753e": "removeCertifier(address)", "0xd299dac0": "blake2b(bytes,bytes,uint64)", "0x41395efa": "dgxBalance()", "0xac1b14ff": "proxyCall(uint256)", "0x7a6ce2e1": "getMsgSender()", "0x3855dcd6": "getContrarians_by_index(uint256)", "0xe6febc9b": "investorWithdraw(uint256)", "0xe6e91cfc": "voidFailedPayment(uint256)", "0x547eeac1": "acceptTransfer()", "0x9824425a": "takeOrder(uint256,uint256,uint256,uint256)", "0xdf25ee23": "getIndexId(address,bytes)", "0x0f3d7c3e": "release(string,uint32[3],string,string,string)", "0x15cff546": "isOperationBlocked()", "0x0b927666": "order(address,uint256,address,uint256,uint256,uint256)", "0x00ce2057": "triggerPayment()", "0x9a9c29f6": "settle(uint256,uint256)", "0x0f096163": "Chainy()", "0x2f5a5c5b": "timegame()", "0x900d85fa": "updatePreReleaseTree(bytes32)", "0x1cbd0519": "accountLevel(address)", "0x29a065bd": "getLOg(uint256)", "0xcec95aa1": "getReleaseHashForPackage(string,uint256)", "0x41524433": "sellKissBTCWithCallback(uint256,address,uint256)", "0x5e431709": "sealedBids(address,bytes32)", "0xf55b23c0": "externalLeave()", "0x31375242": "ownerSetTreasury(address)", "0x51b42b00": "deactivate()", "0x5af36e3e": "refund(uint256,uint256)", "0xc5096a69": "feeFor(address,address,uint256)", "0x059a500c": "makeDeposit(uint256)", "0x3c2e7d54": "priv_inMainChain__(int256,int256)", "0x9431f5f0": "withdrawFees(bytes)", "0x91b4a0e7": "Difficulty()", "0x268d50fe": "ownerSetHouseEdge(uint256)", "0x9644fcbd": "changeMembership(address,bool,string)", "0x66aa6f26": "payFee(bytes)", "0x353928d8": "helpRed()", "0x9c1193ea": "GreeterA(bytes)", "0xdd79e33e": "splitIdentifiers(string)", "0x4d268ddd": "payImporterBankForGoodsBought()", "0x656d2f63": "ManagedAccount(address)", "0x1216e771": "expiration(uint64)", "0x36f7cd70": "setPricePerStake(uint256)", "0x7842a3a4": "payReward()", "0x0ae50a39": "GetOwner()", "0xc81caae7": "acceptMember(address,string,string)", "0xe50dce71": "testControllerApproveSetsAllowance()", "0x4b64e492": "execute(address)", "0xd9e947f3": "kickOutMember(address)", "0x35cc59a9": "createSchema(bytes)", "0x2530c905": "rand(uint256)", "0x4894e37f": "__callback(bytes,string,bytes)", "0x70480275": "addAdmin(address)", "0x969cb7c3": "getPublisher(uint256)", "0x4ed4831a": "all(bool[7])", "0x2ef3accc": "getPrice(string,uint256)", "0x67854643": "getGenerationMemberLength(uint256)", "0xe6690fb1": "nextAuction(uint256)", "0x5829d310": "entries(int256)", "0x7fe1dc7e": "getToken(bytes)", "0xe7329e71": "scheduleCall(bytes,bytes,uint256,uint256,uint8,uint256)", "0x41c12a70": "voteNo()", "0x6a28db13": "getQrLength()", "0xdd93890b": "setMeta(uint256,bytes32,bytes32)", "0xa48bdb7c": "results()", "0x9d888e86": "currentVersion()", "0xff81fb91": "unhint(int256,bytes32)", "0x9ec32d45": "challengeWinningOutcome(bytes,uint16)", "0xa0a2f629": "setReferralId(uint256,address)", "0x76577eae": "distributeEarnings()", "0x3e5cee05": "issueIOU(string,uint256,address)", "0xf3c7d275": "prenup(string,string,string,string,string,address,address)", "0x7154ae61": "CheckNumbers(uint8[5])", "0x05de4f07": "getContentParent(uint256)", "0xb81e43fc": "getEventName()", "0xa7eeea37": "NewContributor(uint256)", "0xe816a515": "takeFlight()", "0x05b2b03a": "CertificationCentre(address)", "0x74d4ab27": "fipsRegister()", "0x65fa2f7f": "getLastPrice(uint256)", "0xcc8b34ab": "CrowdCoin()", "0xe2b178a0": "getAuthority()", "0x5fb64fd6": "checkMembership(address)", "0x7948f523": "setAmbiAddress(address,bytes32)", "0xebb71194": "withdrawFees(bytes32)", "0x6545bed3": "Dice(uint256,uint256,uint256,uint256,uint256,uint256,uint256)", "0x7065cb48": "addOwner(address)", "0x913f424c": "_ecMul(uint256,uint256,uint256,uint256)", "0xdbecc372": "Example(uint256)", "0x9f7f760c": "SimpleDice()", "0xca94692d": "abiSignature()", "0x61ba3377": "WatchLastTime()", "0x20e647e1": "checkBetColor(uint8,address,bytes32,bytes32)", "0x0a3b0a4f": "add(address)", "0xc51cf179": "calcBaseFeeForShares(uint256)", "0x8baced64": "isInPool(address)", "0x4dc415de": "reject()", "0x1555e337": "ConferenceCertificate()", "0x9555a942": "withdrawFrom(address,address,uint256)", "0xe1efda6d": "airaSend(address,address,uint256)", "0x8bfc2f33": "delegateDAOTokens(uint256)", "0xb964608d": "get_return_by_level(uint256)", "0x0c1fad51": "setSeedSourceA(address)", "0x98688a95": "Ai()", "0xd930a90b": "testFailMoveBalanceDueToInsufficientFunds()", "0x337b5988": "testSimpleNameRegister()", "0xf06d335e": "_recoverAccount(address,address)", "0x025bbbe5": "newSale(bytes16,uint256,uint256)", "0x984413b8": "_eraseNode(bytes32)", "0x5ea187c9": "BuildByteArray(bytes)", "0xc2a95cc9": "updateTrustSettings(address,uint256)", "0xde0ff7c5": "getEther()", "0x4ac7becf": "SimpleSign()", "0x252786e4": "WatchBlockSizeInEther()", "0xf2016a4f": "updateMinEthPerNotification(uint256)", "0xd743ca38": "newWinner(uint256,address,uint256,uint256,uint256)", "0x9eab5253": "getMembers()", "0x51d38d5f": "addDeveloper(address,string)", "0x930ed251": "getSavedVar()", "0x715ef4ff": "resendFailedPayment(uint256)", "0xb1418cf4": "payHouse()", "0xe1569f6b": "testThrowsSetNotRetractableNotOwner()", "0x4ae9af61": "getBotStats(uint256,uint256)", "0xbf8ecf9c": "authProposals()", "0xc00ca383": "getByOwner(address,uint256)", "0xc8e7ca2e": "getMsgData()", "0x711953ef": "setGameAddress(address)", "0x63a8dac2": "changeSettings(uint256,uint256,uint256,uint8,uint256,uint256,uint8,uint8)", "0x72c87075": "testBlockHeaderFetch()", "0x4c7a2254": "checkMyWithdraw()", "0xab91c7b0": "queueLength()", "0x25209260": "PrepareRoll(uint256)", "0x58150c8b": "GameRegistry()", "0x75608264": "get_hash(uint8,bytes32)", "0x6510ef4d": "oraclize_query(uint256,string,string[5])", "0xd57a12f5": "testCheckSigs()", "0x3f415772": "releaseExists(bytes32)", "0xda25c0cd": "ThisExternalAssembly()", "0xf239e528": "sendOneEtherHome()", "0xc4321adb": "investInTheSystem(uint256)", "0x4fab2ca4": "testGetFrontend()", "0x05261aea": "finalize(uint256)", "0x576eac66": "setFundingGoal(uint256)", "0xe75528cc": "buyBuilding(uint256,uint256)", "0x6ed43eb0": "getInvestorList(uint256)", "0xb38415f3": "getConfigBytes(bytes)", "0x771ad635": "getContentCred(address,uint256)", "0x93c166ec": "computeEndowment(uint256,uint256,uint256,uint256)", "0xac35caee": "transferWithReference(address,uint256,string)", "0xc6803622": "wasCalled()", "0x8cfd8901": "_incBlock()", "0xfcf0f55b": "eventOracles(bytes32,uint256)", "0x505ff574": "register(address,uint256,bool)", "0xf824384a": "addInvestorAtID(uint256)", "0x6b9f96ea": "flush()", "0xc3d0a564": "getAccountBalance(bytes)", "0x30fd300f": "registerBytes32(address,bytes32)", "0xc3169ef2": "respond(uint256,uint256[4])", "0xcf1cd249": "secureSend(address)", "0x62c335c1": "checkCallback(address,uint256,bytes,bytes)", "0xb599afc8": "totalBetCount()", "0x69433e12": "setExchange(uint256)", "0x899942b8": "Devcon2Token()", "0x4c2d71b3": "setConfigAddress(bytes32,address)", "0xb974b0a3": "allData()", "0x27e8c2d8": "burnUnicornShares()", "0xf639365d": "testSetGet()", "0x2f5d3916": "testControllerApproveTriggersEvent()", "0x938b5f32": "origin()", "0xd60dcb5d": "Switch()", "0xde629235": "getCertificationDocumentAtIndex(address,uint256)", "0x329ce29e": "buyTile(uint256)", "0x59e2d30e": "testThrowBlobStoreNotRegistered()", "0xa005b87b": "NullMapTest()", "0xc13afa91": "object_locations(uint256)", "0x4848b1a5": "setData(uint256,uint256)", "0x80ede329": "getDocumentDetails(uint256)", "0x35d13969": "SendAllMoney()", "0x8040cac4": "testOverflow()", "0x9507d39a": "get(uint256)", "0xc040e6b8": "stage()", "0x18178358": "poke()", "0xfd782de5": "Proxy()", "0xfd68a422": "returnmoneycreator(uint8,uint128)", "0x86a50535": "voteFor(uint256)", "0x44602a7d": "testFallbackReturn()", "0xa230c524": "isMember(address)", "0x3ffbd47f": "register(string,string)", "0x8cecf66e": "_inverse(uint256)", "0x51017702": "isOutcomeSet(bytes32)", "0xd408746a": "GetContractAddr()", "0x20130753": "testThrowSetNotRetractableNotOwner()", "0xa0e2abf7": "getFirstActiveGamble()", "0x7c582304": "updateInvestmentTotal(address,uint256)", "0x95d89b41": "symbol()", "0x1768b436": "ETCSurvey()", "0x6d4ce63c": "get()", "0xc41a360a": "getOwner(uint256)", "0x49942ccb": "scheduleCall(bytes,bytes,uint256,uint256)", "0x6b64c769": "startAuction()", "0x084d72f4": "getWinningOutcome(uint256)", "0xd379be23": "claimer()", "0x41fa4876": "multiBlockRandomGen(uint256,uint256)", "0x5bc7e259": "updateRelease(uint32,uint32,uint32,bytes,bool)", "0x47f3d794": "configure(uint256,uint8,uint256,uint256,uint256,uint256)", "0xe2bbb158": "deposit(uint256,uint256)", "0x953a7fab": "testMoveBalance()", "0xeacc5b3b": "safeSend(address,uint256,uint256)", "0x96f0aa8f": "findNextSecond(uint256,bytes)", "0xc8690233": "pubkey(bytes32)", "0x459f93f7": "getBuyers(uint256,address)", "0xf714de9c": "MultiAccess()", "0xf4a81d08": "getKudosGiven(address)", "0x5aa94a68": "computeResultVoteExtraInvestFeesRate()", "0xdb2a0cb7": "HumanStandardTokenFactory()", "0xdf3a6b10": "testMemberAddedEvent()", "0xce8d054e": "_setupNoCallback()", "0x8ea822d8": "createThings(bytes32[],uint16[],bytes32[],uint16[],uint88)", "0x24fb563f": "PlayerTickets(address,uint256,uint256)", "0x8c0e156d": "scheduleCall(bytes4,uint256,uint256)", "0xef04fdb7": "buyShares(bytes,uint8,uint256,uint256)", "0xa0afd731": "dividendBalance(address)", "0xc3c95c7b": "getMarket(bytes32)", "0x94ed9b77": "append(address,address)", "0xc87b36ed": "disableBetting()", "0x566735d8": "PreVNK(uint256,string,string,uint8)", "0x400aae08": "isInCurrentGeneration(address)", "0x44dd4b5e": "scheduleTransaction(address,uint256,bytes)", "0x48c54b9d": "claimTokens()", "0xe8930efd": "Investors(address)", "0xa6f2ae3a": "buy()", "0x12819817": "setXauForGasCurrator(address)", "0x056e1059": "oraclize_query(uint256,string,string,uint256)", "0x7824407f": "tokenSupply()", "0x7f0c949c": "setJurisdication(string)", "0x2e817963": "set_sdl(address)", "0xaee84f6b": "setTime(address,uint256)", "0x3c0dde1c": "_addPools(address,address)", "0xf8bd526e": "setCoinageContract(address)", "0x04b07a5e": "removeUpdater(address)", "0x11149ada": "getProof(uint256)", "0x4306cc3f": "queryEarnings(address)", "0x55241077": "setValue(uint256)", "0x492b67ea": "Etherdoc()", "0xadf59f99": "query(uint256,string,string)", "0x951b01c5": "setCertifierDb(address)", "0x8ae986cf": "registrantApprove(address)", "0xfa68b4ce": "lookupISO3116_1_alpha_3(bytes)", "0x7fdc8290": "isUnderscore(bytes1)", "0x89495172": "convictFinal(uint256,uint256)", "0x93e02d13": "FallenLeaders()", "0x3e476053": "moveFunds(address,uint256)", "0x8894dd2b": "addEther()", "0x8f03850b": "numContributors()", "0xbfc3cd2f": "testFailChargeMoreThanApproved()", "0x1d82e9c7": "EXTRA_GAS()", "0x278ecde1": "refund(uint256)", "0x0f825673": "deleteCoupon(string)", "0xa324ad24": "getMonth(uint256)", "0xd628e0a6": "WatchBalance()", "0xb238ad0e": "getDaysInMonth(uint8,uint16)", "0xd6febde8": "buy(uint256,uint256)", "0x370ec1c5": "_fillOrder(address,uint256)", "0x4c33fe94": "cancel(address)", "0xcdd13701": "getEventHashes(uint256[256])", "0xe1bc3003": "reveal(bytes,string)", "0xa2e62045": "update()", "0x75f45878": "scheduleCall(bytes,bytes,uint256)", "0xd2756e11": "finalizeNumber(uint256)", "0x48519189": "MonedaAlcala(string,string)", "0x009b9369": "getVoteNumber(uint256)", "0xdaa283c8": "__callback(bytes,string)", "0xfcce2622": "challengeAnswer(uint256,bytes)", "0xac18de43": "removeManager(address)", "0x16d9356f": "oraclize_query(string,string[4])", "0xd1734eac": "isInNextGeneration(address)", "0x524fa7b9": "whitelistAdd(address)", "0xa5eb7a4e": "operated()", "0xb0aab296": "getNextNode(bytes)", "0x1982ed58": "ChangeReuseCashInHarware(bool,uint16,uint16)", "0xdf811d7d": "numberOfPlayersInCurrentRound()", "0xca7dc5b1": "getNumberOfTweets()", "0x488b3538": "shares(address,bytes32,int256)", "0xebd83378": "get_blocks_for(uint256)", "0x399fdb86": "testFailNormalWhitelistReset()", "0xca0c1e62": "computeMerkle(int256,int256,int256[],int256,int256,int256[])", "0x8963dab4": "getNodeId(bytes,bytes)", "0x7d94792a": "seed()", "0xbcf175c8": "oraclize_cbAddress()", "0x38eee93e": "scheduleCall(address,bytes,bytes,uint16,uint8,uint256[5])", "0xa08d3f83": "Etheropt(uint256,string,uint256,uint256,bytes32,address,int256[])", "0xae47a290": "changeMaxBet(uint256)", "0xd12c1e28": "badgesOf(address)", "0x001f8d11": "removePackage(bytes32,string)", "0x54fd4d50": "version()", "0x89abeb19": "ProcessGameExt(uint256)", "0x3dd7c1b9": "newProduct(string,string,uint256,uint256)", "0xa396541e": "getPongvalTxRetrievalAttempted()", "0xcc8af0fe": "bytesToUInt(bytes,bytes)", "0x983b94fb": "finalizeAuction(bytes32)", "0x3df91162": "getUpdatable(bytes20)", "0x045236b4": "getChainyData(string)", "0x9c172f87": "EthVentures4()", "0x996a4be3": "uintToBytes(uint256,uint256)", "0x775a8f5e": "toBytes(uint256)", "0xb6db75a0": "isAdmin()", "0x0b6fcdb0": "getEnforceRevisions(bytes32)", "0x29d6f899": "BetOnBlue()", "0xe6cbcba9": "PlusOnePonzi()", "0xc9030ea0": "addMember(address,bool)", "0x8f283970": "changeAdmin(address)", "0x670c884e": "setup(address,uint256,uint256,uint256,address)", "0x808ab1d6": "getCertificationDbCount()", "0x018f5472": "isAUser(address)", "0x59c87d70": "request(bytes32)", "0x407cfe5e": "get_all_players()", "0x33f472b9": "MPO()", "0x662dbe96": "getNodeHeight(bytes)", "0x60b1e173": "getProof(uint256,address,address)", "0xf25eb5c1": "removeReverse()", "0x1d065dde": "_transferWithReward(address,address,uint256)", "0x65343fcb": "TrustEth()", "0xaa237e21": "set(bool,uint256)", "0x60e519c0": "computeMarginAmount()", "0xd9597016": "multisetCustomGasPrice(uint256[],address[])", "0x4f10acc1": "updateGoldFeeData(uint256)", "0x1e9ea66a": "balanceEther10000000(uint256)", "0xffe34512": "getNumChannels(address)", "0x71dd8862": "IndexOf()", "0xdd9dd688": "calcStopPrice()", "0x934354e7": "finishSpin()", "0x26881518": "setupFee(address)", "0x5eb3f639": "assertTrue(bool,bytes)", "0xe9540395": "getRewardDivisor()", "0x8e4afa51": "checkTransferToICAP(bytes32,uint256)", "0xb5b33eda": "scheduleCall(address,uint256)", "0x3d6a32bd": "createTradeContract(address,uint256,uint256,uint256,bool,bool)", "0x5fd9dff6": "allowance(address,address,bytes)", "0x0d244d68": "setNotRetractable(bytes32)", "0xe63697c8": "withdraw(uint256,address,uint256)", "0x3e5087cc": "testBasicThing()", "0xee77fe86": "scheduleCall(address,bytes4,bytes,uint256,uint256,uint8)", "0xb1cc4348": "placeWager()", "0xb95594e5": "lineOfPlayers(uint256)", "0xa9cc4718": "fail()", "0x54385526": "setStatus(uint8,uint8,string)", "0xb45c48dc": "Security_AddPasswordSha3HashToBankAccount(bytes)", "0xace51abc": "helperVerifyHash__(uint256,int256,int256[],int256,uint256,int256,int256[],int256)", "0x1f5d0b4c": "address(address,address,uint256)", "0x7acbfb65": "setOwner(uint256,uint256)", "0x3462f32d": "execWithGasLimit(bytes32,bytes32,uint256,uint256)", "0xac04f5a7": "append(address)", "0x2fcb6628": "_stringGas(string,string)", "0xe977992d": "Doubler()", "0xc57a050e": "fairandeasy()", "0x412664ae": "sendToken(address,uint256)", "0x0afa9fb9": "contains(int256,address)", "0xb69ef8a8": "balance()", "0x264c8e9a": "whatWasTheVal()", "0x255016c8": "checkIfExploded()", "0xd716222c": "is_owner(uint256,address)", "0xc398f030": "expire(uint256,uint8,bytes,bytes,bytes)", "0xb7d454a4": "setNotTransferable(bytes32)", "0x4789aaef": "EthereumDice()", "0xc0171112": "timestamp(uint64)", "0x4f60f334": "multiAccessAddOwner(address)", "0x80aed05f": "LooneyDice()", "0x55ba343f": "getMarket(bytes)", "0x943b0747": "RewardOffer(address,address,bytes,uint256,uint256,uint128,uint256)", "0xa27c672a": "owner_reveal_and_commit(uint8,bytes32,bytes32)", "0x8f731077": "extractAllowanceRecordLength(address)", "0xc3d345c4": "getHangoutAddress()", "0xa8978434": "softResolveAnswer(uint256)", "0x7cef6047": "getNavHistory(uint256)", "0xfae14192": "changeFeePercentage(uint256)", "0x2ddbc04a": "play2(address,uint256)", "0x3fb27b85": "seal()", "0xe8038e25": "TokenSale(uint256,uint256,address)", "0x8d92fdf3": "withdrawAsset(uint256)", "0x8579cbde": "getPrice(string,uint256,address)", "0x0d61b519": "executeProposal(uint256)", "0x63a599a4": "emergencyStop()", "0x661e3605": "ConstructorContract(uint256)", "0xfd7c460d": "ciberLottery()", "0x1f83f440": "getPaymentByAddress(address)", "0xdcf73856": "generateGroups()", "0x3c6e03d7": "thewhalegame()", "0x271cd760": "getPackageDb()", "0x53fefd7d": "changeMaxDeposit(uint256)", "0xae169a50": "claimReward(uint256)", "0x6da84ec0": "calcMarketFee(bytes32,uint256)", "0xb16562fe": "fipsRegister(address,bytes)", "0x041fe13d": "onEtherandomSeed(bytes32,bytes32)", "0x4a617faa": "shaBid(bytes32,uint256,bytes32)", "0x052b2aa7": "getRegistrants()", "0x0ff0a4df": "reFund()", "0xe56b9dce": "GetPrize(uint256)", "0x8eec99c8": "setNewAdmin(address)", "0xffcce369": "changeIPFSHash(string)", "0x40fdf515": "issuetender(address,uint256,uint256)", "0xef4bdfdd": "Set_your_game_number_between_1_15(string)", "0xa991cb0e": "respond(uint256)", "0x617fba04": "getRecord(address)", "0x475a9fa9": "issueTokens(address,uint256)", "0xd30fbd0d": "safeSubtract(uint256,uint256)", "0xe54d4051": "receiveInteger(bytes,uint256,uint16)", "0x8023ffbd": "getOverallSize()", "0x8390b02a": "rfindPtr(uint256,uint256,uint256,uint256)", "0x11e99c22": "arrival()", "0x10c1952f": "setLocked()", "0x039a21b8": "tryExecute(address,bytes,uint256)", "0x201dcd7a": "newChallenge(uint256,uint256)", "0x5aebfd14": "createFile(bytes)", "0xa6b1caa3": "gasScalar(uint256)", "0x200538c6": "DTE()", "0xd1738b72": "wroomWroom()", "0x22f607f6": "Escrow()", "0x7e81b6aa": "KingdomFactory()", "0x901d7775": "voteOutMasterKey(address)", "0xd6af9411": "Rouleth()", "0x7b0383b2": "initializeDispute(uint256)", "0x70c9edb7": "BTCRelayTools(address)", "0xe9c31315": "checkBetParity(uint8,address,bytes32,bytes32)", "0xee82ac5e": "getBlockHash(uint256)", "0x727089f1": "extractAllowanceLength()", "0x8bbb5af7": "test1Fails()", "0x47872b42": "unsealBid(bytes32,uint256,bytes32)", "0x46b04e53": "PlayerInfoPerZone(uint256,uint256)", "0x752bacce": "getExecPrice()", "0x89d8ca67": "drawPot(bytes32,bytes32)", "0xc23697a8": "check(address)", "0x0af658ca": "personUpdateActivity(uint256,bool)", "0x24d4e90a": "ln(uint256)", "0x09d33f1d": "addRequest(address,uint256)", "0xc913b552": "getVersions(bytes)", "0xbb3b8dca": "getCertificateHash(bytes)", "0x3809c0bf": "doInfinite()", "0x853552d7": "_slotAddNew(address)", "0xccf1ab9b": "usurpation()", "0xe7dafdb6": "transfer_token(address,address,uint256)", "0x0c77a697": "claimFounders()", "0xda82a035": "sweepCommission()"} \ No newline at end of file diff --git a/cmd/clef/README.md b/cmd/clef/README.md new file mode 100644 index 000000000000..027c22c98f10 --- /dev/null +++ b/cmd/clef/README.md @@ -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: +![image](sign_flow.png) + +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: | diff --git a/cmd/clef/docs/qubes/clef_qubes_http.png b/cmd/clef/docs/qubes/clef_qubes_http.png new file mode 100644 index 000000000000..a641e1987fdf Binary files /dev/null and b/cmd/clef/docs/qubes/clef_qubes_http.png differ diff --git a/cmd/clef/docs/qubes/clef_qubes_qrexec.png b/cmd/clef/docs/qubes/clef_qubes_qrexec.png new file mode 100644 index 000000000000..f57fc8933ed5 Binary files /dev/null and b/cmd/clef/docs/qubes/clef_qubes_qrexec.png differ diff --git a/cmd/clef/docs/qubes/qrexec-example.png b/cmd/clef/docs/qubes/qrexec-example.png new file mode 100644 index 000000000000..0d86fde19d8b Binary files /dev/null and b/cmd/clef/docs/qubes/qrexec-example.png differ diff --git a/cmd/clef/docs/qubes/qubes-client.py b/cmd/clef/docs/qubes/qubes-client.py new file mode 100644 index 000000000000..93a74b899b21 --- /dev/null +++ b/cmd/clef/docs/qubes/qubes-client.py @@ -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() + diff --git a/cmd/clef/docs/qubes/qubes.Clefsign b/cmd/clef/docs/qubes/qubes.Clefsign new file mode 100644 index 000000000000..9b5af7b4fe00 --- /dev/null +++ b/cmd/clef/docs/qubes/qubes.Clefsign @@ -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 diff --git a/cmd/clef/docs/qubes/qubes_newaccount-1.png b/cmd/clef/docs/qubes/qubes_newaccount-1.png new file mode 100644 index 000000000000..598dbbee7a99 Binary files /dev/null and b/cmd/clef/docs/qubes/qubes_newaccount-1.png differ diff --git a/cmd/clef/docs/qubes/qubes_newaccount-2.png b/cmd/clef/docs/qubes/qubes_newaccount-2.png new file mode 100644 index 000000000000..cd762a1934a8 Binary files /dev/null and b/cmd/clef/docs/qubes/qubes_newaccount-2.png differ diff --git a/cmd/clef/docs/setup.md b/cmd/clef/docs/setup.md new file mode 100644 index 000000000000..33d2b0381f06 --- /dev/null +++ b/cmd/clef/docs/setup.md @@ -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. + +![Example](qubes/qrexec-example.png) + +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 + +![Clef via qrexec](qubes/clef_qubes_qrexec.png) + +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: + +![one](qubes/qubes_newaccount-1.png) + +Followed by a GTK-dialog to approve the operation + +![two](qubes/qubes_newaccount-2.png) + +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. + +![Clef via http](qubes/clef_qubes_http.png) + + + + +## 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. + diff --git a/cmd/clef/extapi_changelog.md b/cmd/clef/extapi_changelog.md new file mode 100644 index 000000000000..2014e90ae4c2 --- /dev/null +++ b/cmd/clef/extapi_changelog.md @@ -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. diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md new file mode 100644 index 000000000000..7d2a897ea2c3 --- /dev/null +++ b/cmd/clef/intapi_changelog.md @@ -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. diff --git a/cmd/clef/main.go b/cmd/clef/main.go new file mode 100644 index 000000000000..9a1ae91acb15 --- /dev/null +++ b/cmd/clef/main.go @@ -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 . + +// 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: "", + 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: "
", + 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) + 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/ + + +**/ diff --git a/cmd/clef/pythonsigner.py b/cmd/clef/pythonsigner.py new file mode 100644 index 000000000000..46fa23bd8c1b --- /dev/null +++ b/cmd/clef/pythonsigner.py @@ -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:]) diff --git a/cmd/clef/rules.md b/cmd/clef/rules.md new file mode 100644 index 000000000000..327ba765c5fb --- /dev/null +++ b/cmd/clef/rules.md @@ -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 ` + +##### 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 ` +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" + } + +``` \ No newline at end of file diff --git a/cmd/clef/sign_flow.png b/cmd/clef/sign_flow.png new file mode 100644 index 000000000000..9c0f3cc5d58b Binary files /dev/null and b/cmd/clef/sign_flow.png differ diff --git a/cmd/clef/tutorial.md b/cmd/clef/tutorial.md new file mode 100644 index 000000000000..d59e08ac7ac7 --- /dev/null +++ b/cmd/clef/tutorial.md @@ -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 : +* 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 : +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" +``` diff --git a/cmd/ethkey/main.go b/cmd/ethkey/main.go index 2a9e5ee4838b..4127f5566f0f 100644 --- a/cmd/ethkey/main.go +++ b/cmd/ethkey/main.go @@ -53,10 +53,6 @@ var ( Name: "json", Usage: "output JSON instead of human-readable format", } - messageFlag = cli.StringFlag{ - Name: "message", - Usage: "the file that contains the message to sign/verify", - } ) func main() { diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 8a7399840cf7..99919304ae14 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -21,12 +21,12 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/big" "os" + goruntime "runtime" "runtime/pprof" "time" - goruntime "runtime" - "github.com/ethereum/go-ethereum/cmd/evm/internal/compiler" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" @@ -76,6 +76,7 @@ func runCmd(ctx *cli.Context) error { logconfig := &vm.LogConfig{ DisableMemory: ctx.GlobalBool(DisableMemoryFlag.Name), DisableStack: ctx.GlobalBool(DisableStackFlag.Name), + Debug: ctx.GlobalBool(DebugFlag.Name), } var ( @@ -83,8 +84,9 @@ func runCmd(ctx *cli.Context) error { debugLogger *vm.StructLogger statedb *state.StateDB chainConfig *params.ChainConfig - sender = common.StringToAddress("sender") - receiver = common.StringToAddress("receiver") + sender = common.BytesToAddress([]byte("sender")) + receiver = common.BytesToAddress([]byte("receiver")) + blockNumber uint64 ) if ctx.GlobalBool(MachineFlag.Name) { tracer = NewJSONLogger(logconfig, os.Stdout) @@ -100,6 +102,7 @@ func runCmd(ctx *cli.Context) error { genesis := gen.ToBlock(db) statedb, _ = state.New(genesis.Root(), state.NewDatabase(db)) chainConfig = gen.Config + blockNumber = gen.Number } else { db, _ := ethdb.NewMemDatabase() statedb, _ = state.New(common.Hash{}, state.NewDatabase(db)) @@ -155,11 +158,12 @@ func runCmd(ctx *cli.Context) error { initialGas := ctx.GlobalUint64(GasFlag.Name) runtimeConfig := runtime.Config{ - Origin: sender, - State: statedb, - GasLimit: initialGas, - GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), - Value: utils.GlobalBig(ctx, ValueFlag.Name), + Origin: sender, + State: statedb, + GasLimit: initialGas, + GasPrice: utils.GlobalBig(ctx, PriceFlag.Name), + Value: utils.GlobalBig(ctx, ValueFlag.Name), + BlockNumber: new(big.Int).SetUint64(blockNumber), EVMConfig: vm.Config{ Tracer: tracer, Debug: ctx.GlobalBool(DebugFlag.Name) || ctx.GlobalBool(MachineFlag.Name), @@ -234,9 +238,7 @@ Gas used: %d `, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas) } - if tracer != nil { - tracer.CaptureEnd(ret, initialGas-leftOverGas, execTime, err) - } else { + if tracer == nil { fmt.Printf("0x%x\n", ret) if err != nil { fmt.Printf(" error: %v\n", err) diff --git a/cmd/geth/bugcmd.go b/cmd/geth/bugcmd.go index ce9dbe6c0a75..51187ac90e25 100644 --- a/cmd/geth/bugcmd.go +++ b/cmd/geth/bugcmd.go @@ -49,15 +49,17 @@ func reportBug(ctx *cli.Context) error { // execute template and write contents to buff var buff bytes.Buffer - fmt.Fprintln(&buff, header) + fmt.Fprintln(&buff, "#### System information") + fmt.Fprintln(&buff) fmt.Fprintln(&buff, "Version:", params.Version) fmt.Fprintln(&buff, "Go Version:", runtime.Version()) fmt.Fprintln(&buff, "OS:", runtime.GOOS) printOSDetails(&buff) + fmt.Fprintln(&buff, header) // open a new GH issue 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 } @@ -97,13 +99,15 @@ func printCmdOut(w io.Writer, prefix, path string, args ...string) { 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 + + +#### Actual behaviour + + +#### Steps to reproduce the behaviour + -#### What did you do? - -#### What did you expect to see? - -#### What did you see instead? - -#### System details +#### Backtrace ` diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 061384d1b369..09d9c493d1cd 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/console" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethclient" @@ -46,8 +45,6 @@ const ( var ( // Git SHA1 commit hash of the release (set via linker flags) gitCommit = "" - // Ethereum address of the Geth release oracle. - relOracle = common.HexToAddress("0xfa7b9770ca4cb04296cac84f37736d4041251cdf") // The app that holds all commands and flags. app = utils.NewApp(gitCommit, "the go-ethereum command line interface") // flags that configure the node @@ -149,7 +146,7 @@ func init() { // Initialize the CLI app and start Geth app.Action = geth 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{ // See chaincmd.go: initCommand, @@ -226,6 +223,8 @@ func geth(ctx *cli.Context) error { // it unlocks any requested accounts, and starts the RPC/IPC interfaces and the // miner. func startNode(ctx *cli.Context, stack *node.Node) { + debug.Memsize.Add("node", stack) + // Start up the node itself utils.StartNode(stack) @@ -244,7 +243,7 @@ func startNode(ctx *cli.Context, stack *node.Node) { stack.AccountManager().Subscribe(events) go func() { - // Create an chain state reader for self-derivation + // Create a chain state reader for self-derivation rpcClient, err := stack.Attach() if err != nil { utils.Fatalf("Failed to attach to self: %v", err) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index a1558c2330c9..d934c6b021ed 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -33,7 +33,7 @@ import ( var AppHelpTemplate = `NAME: {{.App.Name}} - {{.App.Usage}} - Copyright 2013-2017 The go-ethereum Authors + Copyright 2013-2018 The go-ethereum Authors USAGE: {{.App.HelpName}} [options]{{if .App.Commands}} command [command options]{{end}} {{if .App.ArgsUsage}}{{.App.ArgsUsage}}{{else}}[arguments...]{{end}} diff --git a/cmd/puppeth/module_dashboard.go b/cmd/puppeth/module_dashboard.go index 3832b247f8ef..4f9e88899a76 100644 --- a/cmd/puppeth/module_dashboard.go +++ b/cmd/puppeth/module_dashboard.go @@ -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)) } -// 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. type dashboardInfos struct { host string diff --git a/cmd/puppeth/module_explorer.go b/cmd/puppeth/module_explorer.go index 427134153b8c..bb43e5fe4e9b 100644 --- a/cmd/puppeth/module_explorer.go +++ b/cmd/puppeth/module_explorer.go @@ -168,7 +168,7 @@ func (info *explorerInfos) Report() map[string]string { 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. func checkExplorer(client *sshClient, network string) (*explorerInfos, error) { // Inspect a possible block explorer container on the host diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go index 976bf04d001b..8365bf47d039 100644 --- a/cmd/puppeth/module_faucet.go +++ b/cmd/puppeth/module_faucet.go @@ -30,7 +30,7 @@ import ( "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. var faucetDockerfile = ` 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)) } -// 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. type faucetInfos struct { node *nodeInfos @@ -181,7 +181,7 @@ func (info *faucetInfos) Report() map[string]string { 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. func checkFaucet(client *sshClient, network string) (*faucetInfos, error) { // Inspect a possible faucet container on the host diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go index 2609fd976ef6..1e1767c04b30 100644 --- a/cmd/puppeth/module_node.go +++ b/cmd/puppeth/module_node.go @@ -198,7 +198,7 @@ func (info *nodeInfos) Report() map[string]string { 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. func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) { kind := "bootnode" diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 4f3d81f5d2aa..ef5f6a9f0846 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -64,7 +64,7 @@ var ( {{if .cmd.Description}}{{.cmd.Description}} {{end}}{{if .cmd.Subcommands}} SUBCOMMANDS: - {{range .cmd.Subcommands}}{{.cmd.Name}}{{with .cmd.ShortName}}, {{.cmd}}{{end}}{{ "\t" }}{{.cmd.Usage}} + {{range .cmd.Subcommands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{end}}{{end}}{{if .categorizedFlags}} {{range $idx, $categorized := .categorizedFlags}}{{$categorized.Name}} OPTIONS: {{range $categorized.Flags}}{{"\t"}}{{.}} @@ -158,11 +158,11 @@ var ( } FastSyncFlag = cli.BoolFlag{ Name: "fast", - Usage: "Enable fast syncing through state downloads", + Usage: "Enable fast syncing through state downloads (replaced by --syncmode)", } LightModeFlag = cli.BoolFlag{ Name: "light", - Usage: "Enable light client mode", + Usage: "Enable light client mode (replaced by --syncmode)", } defaultSyncMode = eth.DefaultConfig.SyncMode SyncModeFlag = TextMarshalerFlag{ diff --git a/common/compiler/solidity.go b/common/compiler/solidity.go index 234714a2b9c3..4d04b5108c30 100644 --- a/common/compiler/solidity.go +++ b/common/compiler/solidity.go @@ -102,6 +102,33 @@ func SolidityVersion(solc string) (*Solidity, error) { return s, nil } +func New(solcPath string) (sol *Solidity, err error) { + // set default solc + if len(solcPath) == 0 { + solcPath = "solc" + } + solcPath, err = exec.LookPath(solcPath) + if err != nil { + return + } + + cmd := exec.Command(solcPath, "--version") + var out bytes.Buffer + cmd.Stdout = &out + err = cmd.Run() + if err != nil { + return + } + fullVersion := out.String() + version := versionRegexp.FindString(fullVersion) + + sol = &Solidity{ + Path: solcPath, + Version: version, + FullVersion: fullVersion} + return +} + // CompileSolidityString builds and returns all the contracts contained within a source string. func CompileSolidityString(solc, source string) (map[string]*Contract, error) { if len(source) == 0 { @@ -192,3 +219,8 @@ func slurpFiles(files []string) (string, error) { } return concat.String(), nil } + +// Compile builds and returns all the contracts contained within a source string. +func (sol *Solidity) Compile(source string) (map[string]*Contract, error) { + return CompileSolidityString("solc", source) +} \ No newline at end of file diff --git a/common/types.go b/common/types.go index fdc67480c234..0b94fb2c250b 100644 --- a/common/types.go +++ b/common/types.go @@ -18,10 +18,12 @@ package common import ( "encoding/hex" + "encoding/json" "fmt" "math/big" "math/rand" "reflect" + "strings" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto/sha3" @@ -45,9 +47,8 @@ func BytesToHash(b []byte) Hash { h.SetBytes(b) return h } -func StringToHash(s string) Hash { return BytesToHash([]byte(s)) } -func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } -func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } +func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } +func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } // Get the string representation of the underlying hash func (h Hash) Str() string { return string(h[:]) } @@ -143,9 +144,8 @@ func BytesToAddress(b []byte) Address { a.SetBytes(b) return a } -func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } -func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } -func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } +func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } +func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) } // IsHexAddress verifies whether a string can represent a valid hex-encoded // Ethereum address or not. @@ -240,3 +240,63 @@ func (a *UnprefixedAddress) UnmarshalText(input []byte) error { func (a UnprefixedAddress) MarshalText() ([]byte, error) { return []byte(hex.EncodeToString(a[:])), nil } + +// MixedcaseAddress retains the original string, which may or may not be +// correctly checksummed +type MixedcaseAddress struct { + addr Address + original string +} + +// NewMixedcaseAddress constructor (mainly for testing) +func NewMixedcaseAddress(addr Address) MixedcaseAddress { + return MixedcaseAddress{addr: addr, original: addr.Hex()} +} + +// NewMixedcaseAddressFromString is mainly meant for unit-testing +func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) { + if !IsHexAddress(hexaddr) { + return nil, fmt.Errorf("Invalid address") + } + a := FromHex(hexaddr) + return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil +} + +// UnmarshalJSON parses MixedcaseAddress +func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error { + if err := hexutil.UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil { + return err + } + return json.Unmarshal(input, &ma.original) +} + +// MarshalJSON marshals the original value +func (ma *MixedcaseAddress) MarshalJSON() ([]byte, error) { + if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") { + return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:])) + } + return json.Marshal(fmt.Sprintf("0x%s", ma.original)) +} + +// Address returns the address +func (ma *MixedcaseAddress) Address() Address { + return ma.addr +} + +// String implements fmt.Stringer +func (ma *MixedcaseAddress) String() string { + if ma.ValidChecksum() { + return fmt.Sprintf("%s [chksum ok]", ma.original) + } + return fmt.Sprintf("%s [chksum INVALID]", ma.original) +} + +// ValidChecksum returns true if the address has valid checksum +func (ma *MixedcaseAddress) ValidChecksum() bool { + return ma.original == ma.addr.Hex() +} + +// Original returns the mixed-case input string +func (ma *MixedcaseAddress) Original() string { + return ma.original +} diff --git a/common/types_test.go b/common/types_test.go index db636812ce8a..9e0c5be3ad2e 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -18,6 +18,7 @@ package common import ( "encoding/json" + "math/big" "strings" "testing" @@ -149,3 +150,46 @@ func BenchmarkAddressHex(b *testing.B) { testAddr.Hex() } } + +func TestMixedcaseAccount_Address(t *testing.T) { + + // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md + // Note: 0X{checksum_addr} is not valid according to spec above + + var res []struct { + A MixedcaseAddress + Valid bool + } + if err := json.Unmarshal([]byte(`[ + {"A" : "0xae967917c465db8578ca9024c205720b1a3651A9", "Valid": false}, + {"A" : "0xAe967917c465db8578ca9024c205720b1a3651A9", "Valid": true}, + {"A" : "0XAe967917c465db8578ca9024c205720b1a3651A9", "Valid": false}, + {"A" : "0x1111111111111111111112222222222223333323", "Valid": true} + ]`), &res); err != nil { + t.Fatal(err) + } + + for _, r := range res { + if got := r.A.ValidChecksum(); got != r.Valid { + t.Errorf("Expected checksum %v, got checksum %v, input %v", r.Valid, got, r.A.String()) + } + } + + //These should throw exceptions: + var r2 []MixedcaseAddress + for _, r := range []string{ + `["0x11111111111111111111122222222222233333"]`, // Too short + `["0x111111111111111111111222222222222333332"]`, // Too short + `["0x11111111111111111111122222222222233333234"]`, // Too long + `["0x111111111111111111111222222222222333332344"]`, // Too long + `["1111111111111111111112222222222223333323"]`, // Missing 0x + `["x1111111111111111111112222222222223333323"]`, // Missing 0 + `["0xG111111111111111111112222222222223333323"]`, //Non-hex + } { + if err := json.Unmarshal([]byte(r), &r2); err == nil { + t.Errorf("Expected failure, input %v", r) + } + + } + +} diff --git a/compression/rle/read_write.go b/compression/rle/read_write.go deleted file mode 100644 index 0e7ad90aecb0..000000000000 --- a/compression/rle/read_write.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2014 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 . - -// Package rle implements the run-length encoding used for Ethereum data. -package rle - -import ( - "bytes" - "errors" - - "github.com/ethereum/go-ethereum/crypto" -) - -const ( - token byte = 0xfe - emptyShaToken = 0xfd - emptyListShaToken = 0xfe - tokenToken = 0xff -) - -var empty = crypto.Keccak256([]byte("")) -var emptyList = crypto.Keccak256([]byte{0x80}) - -func Decompress(dat []byte) ([]byte, error) { - buf := new(bytes.Buffer) - - for i := 0; i < len(dat); i++ { - if dat[i] == token { - if i+1 < len(dat) { - switch dat[i+1] { - case emptyShaToken: - buf.Write(empty) - case emptyListShaToken: - buf.Write(emptyList) - case tokenToken: - buf.WriteByte(token) - default: - buf.Write(make([]byte, int(dat[i+1]-2))) - } - i++ - } else { - return nil, errors.New("error reading bytes. token encountered without proceeding bytes") - } - } else { - buf.WriteByte(dat[i]) - } - } - - return buf.Bytes(), nil -} - -func compressChunk(dat []byte) (ret []byte, n int) { - switch { - case dat[0] == token: - return []byte{token, tokenToken}, 1 - case len(dat) > 1 && dat[0] == 0x0 && dat[1] == 0x0: - j := 0 - for j <= 254 && j < len(dat) { - if dat[j] != 0 { - break - } - j++ - } - return []byte{token, byte(j + 2)}, j - case len(dat) >= 32: - if dat[0] == empty[0] && bytes.Equal(dat[:32], empty) { - return []byte{token, emptyShaToken}, 32 - } else if dat[0] == emptyList[0] && bytes.Equal(dat[:32], emptyList) { - return []byte{token, emptyListShaToken}, 32 - } - fallthrough - default: - return dat[:1], 1 - } -} - -func Compress(dat []byte) []byte { - buf := new(bytes.Buffer) - - i := 0 - for i < len(dat) { - b, n := compressChunk(dat[i:]) - buf.Write(b) - i += n - } - - return buf.Bytes() -} diff --git a/compression/rle/read_write_test.go b/compression/rle/read_write_test.go deleted file mode 100644 index b36f7907bcba..000000000000 --- a/compression/rle/read_write_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2014 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 . - -package rle - -import ( - "testing" - - checker "gopkg.in/check.v1" -) - -func Test(t *testing.T) { checker.TestingT(t) } - -type CompressionRleSuite struct{} - -var _ = checker.Suite(&CompressionRleSuite{}) - -func (s *CompressionRleSuite) TestDecompressSimple(c *checker.C) { - exp := []byte{0xc5, 0xd2, 0x46, 0x1, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x3, 0xc0, 0xe5, 0x0, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x4, 0x5d, 0x85, 0xa4, 0x70} - res, err := Decompress([]byte{token, 0xfd}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, exp) - - exp = []byte{0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x1, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21} - res, err = Decompress([]byte{token, 0xfe}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, exp) - - res, err = Decompress([]byte{token, 0xff}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, []byte{token}) - - res, err = Decompress([]byte{token, 12}) - c.Assert(err, checker.IsNil) - c.Assert(res, checker.DeepEquals, make([]byte, 10)) - -} diff --git a/consensus/consensus.go b/consensus/consensus.go index be5e661c12bc..5774af1a78eb 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -18,12 +18,13 @@ package consensus import ( + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "math/big" ) // ChainReader defines a small collection of methods needed to access the local diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 99eec82211e2..6a4d1c30b1f7 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -37,7 +37,7 @@ import ( // Ethash proof-of-work protocol constants. var ( FrontierBlockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block - ByzantiumBlockReward *big.Int = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium + ByzantiumBlockReward *big.Int = big.NewInt(0) // Block reward in wei for successfully mining a block upward from Byzantium maxUncles = 2 // Maximum number of uncles allowed in a single block allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks ) @@ -296,6 +296,7 @@ func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, p // given the parent block's time and difficulty. func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { next := new(big.Int).Add(parent.Number, big1) + return calcDifficultyByzantium(time, parent) switch { case config.IsByzantium(next): return calcDifficultyByzantium(time, parent) @@ -309,6 +310,7 @@ func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Heade // Some weird constants to avoid constant memory allocs for them. var ( expDiffPeriod = big.NewInt(100000) + big0 = big.NewInt(0) big1 = big.NewInt(1) big2 = big.NewInt(2) big9 = big.NewInt(9) @@ -326,7 +328,7 @@ func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int { // diff = (parent_diff + // (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99)) // ) + 2^(periodCount - 2) - + return big2; bigTime := new(big.Int).SetUint64(time) bigParentTime := new(big.Int).Set(parent.Time) @@ -336,12 +338,15 @@ func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int { // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9 x.Sub(bigTime, bigParentTime) - x.Div(x, big9) + x.Div(x, big2) if parent.UncleHash == types.EmptyUncleHash { x.Sub(big1, x) } else { x.Sub(big2, x) } + if x.Cmp(big0) == 0 { + x.Sub(x,big1) + } // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99) if x.Cmp(bigMinus99) < 0 { x.Set(bigMinus99) @@ -368,7 +373,7 @@ func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int { // the exponential factor, commonly referred to as "the bomb" // diff = diff + 2^(periodCount - 2) - if periodCount.Cmp(big1) > 0 { + if (periodCount.Cmp(big1) > 0 && false) { y.Sub(periodCount, big2) y.Exp(big2, y, nil) x.Add(x, y) diff --git a/console/bridge.go b/console/bridge.go index b28cc438e210..f2120351c064 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -87,7 +87,7 @@ func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) { // OpenWallet is a wrapper around personal.openWallet which can interpret and // react to certain error messages, such as the Trezor PIN matrix request. func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) { - // Make sure we have an wallet specified to open + // Make sure we have a wallet specified to open if !call.Argument(0).IsString() { throwJSException("first argument must be the wallet URL to open") } diff --git a/core/asm/compiler.go b/core/asm/compiler.go index 18dc0877ffea..c273e7c51b13 100644 --- a/core/asm/compiler.go +++ b/core/asm/compiler.go @@ -17,7 +17,6 @@ package asm import ( - "errors" "fmt" "math/big" "os" @@ -237,19 +236,16 @@ func (c *Compiler) pushBin(v interface{}) { // isPush returns whether the string op is either any of // push(N). func isPush(op string) bool { - return op == "push" + return strings.ToUpper(op) == "PUSH" } // isJump returns whether the string op is jump(i) func isJump(op string) bool { - return op == "jumpi" || op == "jump" + return strings.ToUpper(op) == "JUMPI" || strings.ToUpper(op) == "JUMP" } // toBinary converts text to a vm.OpCode func toBinary(text string) vm.OpCode { - if isPush(text) { - text = "push1" - } return vm.StringToOp(strings.ToUpper(text)) } @@ -264,11 +260,6 @@ func (err compileError) Error() string { return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) } -var ( - errExpBol = errors.New("expected beginning of line") - errExpElementOrLabel = errors.New("expected beginning of line") -) - func compileErr(c token, got, want string) error { return compileError{ got: got, diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 748cdc5c71e5..375823b57b23 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1338,3 +1338,114 @@ func TestLargeReorgTrieGC(t *testing.T) { } } } + +// Benchmarks large blocks with value transfers to non-existing accounts +func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { + var ( + signer = types.HomesteadSigner{} + testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + bankFunds = big.NewInt(100000000000000000) + gspec = Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + testBankAddress: {Balance: bankFunds}, + common.HexToAddress("0xc0de"): { + Code: []byte{0x60, 0x01, 0x50}, + Balance: big.NewInt(0), + }, // push 1, pop + }, + GasLimit: 100e6, // 100 M + } + ) + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + db, _ := ethdb.NewMemDatabase() + genesis := gspec.MustCommit(db) + + blockGenerator := func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{1}) + for txi := 0; txi < numTxs; txi++ { + uniq := uint64(i*numTxs + txi) + recipient := recipientFn(uniq) + //recipient := common.BigToAddress(big.NewInt(0).SetUint64(1337 + uniq)) + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey) + if err != nil { + b.Error(err) + } + block.AddTx(tx) + } + } + + shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, numBlocks, blockGenerator) + b.StopTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Import the shared chain and the original canonical one + diskdb, _ := ethdb.NewMemDatabase() + gspec.MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}) + if err != nil { + b.Fatalf("failed to create tester chain: %v", err) + } + b.StartTimer() + if _, err := chain.InsertChain(shared); err != nil { + b.Fatalf("failed to insert shared chain: %v", err) + } + b.StopTimer() + if got := chain.CurrentBlock().Transactions().Len(); got != numTxs*numBlocks { + b.Fatalf("Transactions were not included, expected %d, got %d", (numTxs * numBlocks), got) + + } + } +} +func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(1337 + nonce)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} +func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(1337)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} +func BenchmarkBlockChain_1x1000Executions(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(0xc0de)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go index ce3031702fde..8d78adb75975 100644 --- a/core/bloombits/matcher.go +++ b/core/bloombits/matcher.go @@ -392,7 +392,7 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests ) - // assign is a helper method fo try to assign a pending bit an an actively + // assign is a helper method fo try to assign a pending bit an actively // listening servicer, or schedule it up for later when one arrives. assign := func(bit uint) { select { diff --git a/core/database_util_test.go b/core/database_util_test.go index ab4e45a4786c..aa87fa6f86ba 100644 --- a/core/database_util_test.go +++ b/core/database_util_test.go @@ -317,7 +317,7 @@ func TestLookupStorage(t *testing.T) { if hash != block.Hash() || number != block.NumberU64() || index != uint64(i) { t.Fatalf("tx #%d [%x]: positional metadata mismatch: have %x/%d/%d, want %x/%v/%v", i, tx.Hash(), hash, number, index, block.Hash(), block.NumberU64(), i) } - if tx.String() != txn.String() { + if tx.Hash() != txn.Hash() { t.Fatalf("tx #%d [%x]: transaction mismatch: have %v, want %v", i, tx.Hash(), txn, tx) } } diff --git a/core/state/database.go b/core/state/database.go index 36926ec69d28..c1b630991ce0 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -26,7 +26,7 @@ import ( lru "github.com/hashicorp/golang-lru" ) -// Trie cache generation limit after which to evic trie nodes from memory. +// Trie cache generation limit after which to evict trie nodes from memory. var MaxTrieCacheGen = uint16(120) const ( @@ -151,9 +151,6 @@ func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, erro return cached.(int), nil } code, err := db.ContractCode(addrHash, codeHash) - if err == nil { - db.codeSizeCache.Add(codeHash, len(code)) - } return len(code), err } diff --git a/core/state/dump.go b/core/state/dump.go index 46e612850a93..072dbbf05300 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -53,7 +53,7 @@ func (self *StateDB) RawDump() Dump { panic(err) } - obj := newObject(nil, common.BytesToAddress(addr), data, nil) + obj := newObject(nil, common.BytesToAddress(addr), data) account := DumpAccount{ Balance: data.Balance.String(), Nonce: data.Nonce, diff --git a/core/state/journal.go b/core/state/journal.go index a89bb3d13ac9..a03ca57dbc64 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -22,11 +22,67 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// journalEntry is a modification entry in the state change journal that can be +// reverted on demand. type journalEntry interface { - undo(*StateDB) + // revert undoes the changes introduced by this journal entry. + revert(*StateDB) + + // dirtied returns the Ethereum address modified by this journal entry. + dirtied() *common.Address +} + +// journal contains the list of state modifications applied since the last state +// commit. These are tracked to be able to be reverted in case of an execution +// exception or revertal request. +type journal struct { + entries []journalEntry // Current changes tracked by the journal + dirties map[common.Address]int // Dirty accounts and the number of changes +} + +// newJournal create a new initialized journal. +func newJournal() *journal { + return &journal{ + dirties: make(map[common.Address]int), + } +} + +// append inserts a new modification entry to the end of the change journal. +func (j *journal) append(entry journalEntry) { + j.entries = append(j.entries, entry) + if addr := entry.dirtied(); addr != nil { + j.dirties[*addr]++ + } +} + +// revert undoes a batch of journalled modifications along with any reverted +// dirty handling too. +func (j *journal) revert(statedb *StateDB, snapshot int) { + for i := len(j.entries) - 1; i >= snapshot; i-- { + // Undo the changes made by the operation + j.entries[i].revert(statedb) + + // Drop any dirty tracking induced by the change + if addr := j.entries[i].dirtied(); addr != nil { + if j.dirties[*addr]--; j.dirties[*addr] == 0 { + delete(j.dirties, *addr) + } + } + } + j.entries = j.entries[:snapshot] +} + +// dirty explicitly sets an address to dirty, even if the change entries would +// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD +// precompile consensus exception. +func (j *journal) dirty(addr common.Address) { + j.dirties[addr]++ } -type journal []journalEntry +// length returns the current number of entries in the journal. +func (j *journal) length() int { + return len(j.entries) +} type ( // Changes to the account trie. @@ -77,16 +133,24 @@ type ( } ) -func (ch createObjectChange) undo(s *StateDB) { +func (ch createObjectChange) revert(s *StateDB) { delete(s.stateObjects, *ch.account) delete(s.stateObjectsDirty, *ch.account) } -func (ch resetObjectChange) undo(s *StateDB) { +func (ch createObjectChange) dirtied() *common.Address { + return ch.account +} + +func (ch resetObjectChange) revert(s *StateDB) { s.setStateObject(ch.prev) } -func (ch suicideChange) undo(s *StateDB) { +func (ch resetObjectChange) dirtied() *common.Address { + return nil +} + +func (ch suicideChange) revert(s *StateDB) { obj := s.getStateObject(*ch.account) if obj != nil { obj.suicided = ch.prev @@ -94,38 +158,60 @@ func (ch suicideChange) undo(s *StateDB) { } } +func (ch suicideChange) dirtied() *common.Address { + return ch.account +} + var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") -func (ch touchChange) undo(s *StateDB) { - if !ch.prev && *ch.account != ripemd { - s.getStateObject(*ch.account).touched = ch.prev - if !ch.prevDirty { - delete(s.stateObjectsDirty, *ch.account) - } - } +func (ch touchChange) revert(s *StateDB) { +} + +func (ch touchChange) dirtied() *common.Address { + return ch.account } -func (ch balanceChange) undo(s *StateDB) { +func (ch balanceChange) revert(s *StateDB) { s.getStateObject(*ch.account).setBalance(ch.prev) } -func (ch nonceChange) undo(s *StateDB) { +func (ch balanceChange) dirtied() *common.Address { + return ch.account +} + +func (ch nonceChange) revert(s *StateDB) { s.getStateObject(*ch.account).setNonce(ch.prev) } -func (ch codeChange) undo(s *StateDB) { +func (ch nonceChange) dirtied() *common.Address { + return ch.account +} + +func (ch codeChange) revert(s *StateDB) { s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } -func (ch storageChange) undo(s *StateDB) { +func (ch codeChange) dirtied() *common.Address { + return ch.account +} + +func (ch storageChange) revert(s *StateDB) { s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } -func (ch refundChange) undo(s *StateDB) { +func (ch storageChange) dirtied() *common.Address { + return ch.account +} + +func (ch refundChange) revert(s *StateDB) { s.refund = ch.prev } -func (ch addLogChange) undo(s *StateDB) { +func (ch refundChange) dirtied() *common.Address { + return nil +} + +func (ch addLogChange) revert(s *StateDB) { logs := s.logs[ch.txhash] if len(logs) == 1 { delete(s.logs, ch.txhash) @@ -135,6 +221,14 @@ func (ch addLogChange) undo(s *StateDB) { s.logSize-- } -func (ch addPreimageChange) undo(s *StateDB) { +func (ch addLogChange) dirtied() *common.Address { + return nil +} + +func (ch addPreimageChange) revert(s *StateDB) { delete(s.preimages, ch.hash) } + +func (ch addPreimageChange) dirtied() *common.Address { + return nil +} diff --git a/core/state/state_object.go b/core/state/state_object.go index b2112bfaeca0..5d203fddd864 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -85,9 +85,7 @@ type stateObject struct { // during the "update" phase of the state transition. dirtyCode bool // true if the code was updated suicided bool - touched bool deleted bool - onDirty func(addr common.Address) // Callback method to mark a state object newly dirty } // empty returns whether the account is considered empty. @@ -105,7 +103,7 @@ type Account struct { } // newObject creates a state object. -func newObject(db *StateDB, address common.Address, data Account, onDirty func(addr common.Address)) *stateObject { +func newObject(db *StateDB, address common.Address, data Account) *stateObject { if data.Balance == nil { data.Balance = new(big.Int) } @@ -119,7 +117,6 @@ func newObject(db *StateDB, address common.Address, data Account, onDirty func(a data: data, cachedStorage: make(Storage), dirtyStorage: make(Storage), - onDirty: onDirty, } } @@ -137,23 +134,17 @@ func (self *stateObject) setError(err error) { func (self *stateObject) markSuicided() { self.suicided = true - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } func (c *stateObject) touch() { - c.db.journal = append(c.db.journal, touchChange{ - account: &c.address, - prev: c.touched, - prevDirty: c.onDirty == nil, + c.db.journal.append(touchChange{ + account: &c.address, }) - if c.onDirty != nil { - c.onDirty(c.Address()) - c.onDirty = nil + if c.address == ripemd { + // Explicitly put it in the dirty-cache, which is otherwise generated from + // flattened journals. + c.db.journal.dirty(c.address) } - c.touched = true } func (c *stateObject) getTrie(db Database) Trie { @@ -187,15 +178,13 @@ func (self *stateObject) GetState(db Database, key common.Hash) common.Hash { } value.SetBytes(content) } - if (value != common.Hash{}) { - self.cachedStorage[key] = value - } + self.cachedStorage[key] = value return value } // SetState updates a value in account storage. func (self *stateObject) SetState(db Database, key, value common.Hash) { - self.db.journal = append(self.db.journal, storageChange{ + self.db.journal.append(storageChange{ account: &self.address, key: key, prevalue: self.GetState(db, key), @@ -206,11 +195,6 @@ func (self *stateObject) SetState(db Database, key, value common.Hash) { func (self *stateObject) setState(key, value common.Hash) { self.cachedStorage[key] = value self.dirtyStorage[key] = value - - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } // updateTrie writes cached storage modifications into the object's storage trie. @@ -274,7 +258,7 @@ func (c *stateObject) SubBalance(amount *big.Int) { } func (self *stateObject) SetBalance(amount *big.Int) { - self.db.journal = append(self.db.journal, balanceChange{ + self.db.journal.append(balanceChange{ account: &self.address, prev: new(big.Int).Set(self.data.Balance), }) @@ -283,17 +267,13 @@ func (self *stateObject) SetBalance(amount *big.Int) { func (self *stateObject) setBalance(amount *big.Int) { self.data.Balance = amount - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } // Return the gas back to the origin. Used by the Virtual machine or Closures func (c *stateObject) ReturnGas(gas *big.Int) {} -func (self *stateObject) deepCopy(db *StateDB, onDirty func(addr common.Address)) *stateObject { - stateObject := newObject(db, self.address, self.data, onDirty) +func (self *stateObject) deepCopy(db *StateDB) *stateObject { + stateObject := newObject(db, self.address, self.data) if self.trie != nil { stateObject.trie = db.db.CopyTrie(self.trie) } @@ -333,7 +313,7 @@ func (self *stateObject) Code(db Database) []byte { func (self *stateObject) SetCode(codeHash common.Hash, code []byte) { prevcode := self.Code(self.db.db) - self.db.journal = append(self.db.journal, codeChange{ + self.db.journal.append(codeChange{ account: &self.address, prevhash: self.CodeHash(), prevcode: prevcode, @@ -345,14 +325,10 @@ func (self *stateObject) setCode(codeHash common.Hash, code []byte) { self.code = code self.data.CodeHash = codeHash[:] self.dirtyCode = true - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } func (self *stateObject) SetNonce(nonce uint64) { - self.db.journal = append(self.db.journal, nonceChange{ + self.db.journal.append(nonceChange{ account: &self.address, prev: self.data.Nonce, }) @@ -361,10 +337,6 @@ func (self *stateObject) SetNonce(nonce uint64) { func (self *stateObject) setNonce(nonce uint64) { self.data.Nonce = nonce - if self.onDirty != nil { - self.onDirty(self.Address()) - self.onDirty = nil - } } func (self *stateObject) CodeHash() []byte { diff --git a/core/state/statedb.go b/core/state/statedb.go index 5473ff8da7cd..a952027d6dae 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -76,7 +76,7 @@ type StateDB struct { // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. - journal journal + journal *journal validRevisions []revision nextRevisionId int @@ -96,6 +96,7 @@ func New(root common.Hash, db Database) (*StateDB, error) { stateObjectsDirty: make(map[common.Address]struct{}), logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), + journal: newJournal(), }, nil } @@ -131,7 +132,7 @@ func (self *StateDB) Reset(root common.Hash) error { } func (self *StateDB) AddLog(log *types.Log) { - self.journal = append(self.journal, addLogChange{txhash: self.thash}) + self.journal.append(addLogChange{txhash: self.thash}) log.TxHash = self.thash log.BlockHash = self.bhash @@ -156,7 +157,7 @@ func (self *StateDB) Logs() []*types.Log { // AddPreimage records a SHA3 preimage seen by the VM. func (self *StateDB) AddPreimage(hash common.Hash, preimage []byte) { if _, ok := self.preimages[hash]; !ok { - self.journal = append(self.journal, addPreimageChange{hash: hash}) + self.journal.append(addPreimageChange{hash: hash}) pi := make([]byte, len(preimage)) copy(pi, preimage) self.preimages[hash] = pi @@ -169,7 +170,7 @@ func (self *StateDB) Preimages() map[common.Hash][]byte { } func (self *StateDB) AddRefund(gas uint64) { - self.journal = append(self.journal, refundChange{prev: self.refund}) + self.journal.append(refundChange{prev: self.refund}) self.refund += gas } @@ -235,10 +236,10 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash { return common.BytesToHash(stateObject.CodeHash()) } -func (self *StateDB) GetState(a common.Address, b common.Hash) common.Hash { - stateObject := self.getStateObject(a) +func (self *StateDB) GetState(addr common.Address, bhash common.Hash) common.Hash { + stateObject := self.getStateObject(addr) if stateObject != nil { - return stateObject.GetState(self.db, b) + return stateObject.GetState(self.db, bhash) } return common.Hash{} } @@ -250,12 +251,12 @@ func (self *StateDB) Database() Database { // StorageTrie returns the storage trie of an account. // The return value is a copy and is nil for non-existent accounts. -func (self *StateDB) StorageTrie(a common.Address) Trie { - stateObject := self.getStateObject(a) +func (self *StateDB) StorageTrie(addr common.Address) Trie { + stateObject := self.getStateObject(addr) if stateObject == nil { return nil } - cpy := stateObject.deepCopy(self, nil) + cpy := stateObject.deepCopy(self) return cpy.updateTrie(self.db) } @@ -271,7 +272,7 @@ func (self *StateDB) HasSuicided(addr common.Address) bool { * SETTERS */ -// AddBalance adds amount to the account associated with addr +// AddBalance adds amount to the account associated with addr. func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) { stateObject := self.GetOrNewStateObject(addr) if stateObject != nil { @@ -279,7 +280,7 @@ func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) { } } -// SubBalance subtracts amount from the account associated with addr +// SubBalance subtracts amount from the account associated with addr. func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) { stateObject := self.GetOrNewStateObject(addr) if stateObject != nil { @@ -308,7 +309,7 @@ func (self *StateDB) SetCode(addr common.Address, code []byte) { } } -func (self *StateDB) SetState(addr common.Address, key common.Hash, value common.Hash) { +func (self *StateDB) SetState(addr common.Address, key, value common.Hash) { stateObject := self.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetState(self.db, key, value) @@ -325,7 +326,7 @@ func (self *StateDB) Suicide(addr common.Address) bool { if stateObject == nil { return false } - self.journal = append(self.journal, suicideChange{ + self.journal.append(suicideChange{ account: &addr, prev: stateObject.suicided, prevbalance: new(big.Int).Set(stateObject.Balance()), @@ -337,7 +338,7 @@ func (self *StateDB) Suicide(addr common.Address) bool { } // -// Setting, updating & deleting state object methods +// Setting, updating & deleting state object methods. // // updateStateObject writes the given object to the trie. @@ -379,7 +380,7 @@ func (self *StateDB) getStateObject(addr common.Address) (stateObject *stateObje return nil } // Insert into the live set. - obj := newObject(self, addr, data, self.MarkStateObjectDirty) + obj := newObject(self, addr, data) self.setStateObject(obj) return obj } @@ -388,7 +389,7 @@ func (self *StateDB) setStateObject(object *stateObject) { self.stateObjects[object.Address()] = object } -// Retrieve a state object or create a new state object if nil +// Retrieve a state object or create a new state object if nil. func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { stateObject := self.getStateObject(addr) if stateObject == nil || stateObject.deleted { @@ -397,22 +398,16 @@ func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { return stateObject } -// MarkStateObjectDirty adds the specified object to the dirty map to avoid costly -// state object cache iteration to find a handful of modified ones. -func (self *StateDB) MarkStateObjectDirty(addr common.Address) { - self.stateObjectsDirty[addr] = struct{}{} -} - // createObject creates a new state object. If there is an existing account with // the given address, it is overwritten and returned as the second return value. func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { prev = self.getStateObject(addr) - newobj = newObject(self, addr, Account{}, self.MarkStateObjectDirty) + newobj = newObject(self, addr, Account{}) newobj.setNonce(0) // sets the object to dirty if prev == nil { - self.journal = append(self.journal, createObjectChange{account: &addr}) + self.journal.append(createObjectChange{account: &addr}) } else { - self.journal = append(self.journal, resetObjectChange{prev: prev}) + self.journal.append(resetObjectChange{prev: prev}) } self.setStateObject(newobj) return newobj, prev @@ -466,18 +461,35 @@ func (self *StateDB) Copy() *StateDB { state := &StateDB{ db: self.db, trie: self.db.CopyTrie(self.trie), - stateObjects: make(map[common.Address]*stateObject, len(self.stateObjectsDirty)), - stateObjectsDirty: make(map[common.Address]struct{}, len(self.stateObjectsDirty)), + stateObjects: make(map[common.Address]*stateObject, len(self.journal.dirties)), + stateObjectsDirty: make(map[common.Address]struct{}, len(self.journal.dirties)), refund: self.refund, logs: make(map[common.Hash][]*types.Log, len(self.logs)), logSize: self.logSize, preimages: make(map[common.Hash][]byte), + journal: newJournal(), } // Copy the dirty states, logs, and preimages + for addr := range self.journal.dirties { + // As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527), + // and in the Finalise-method, there is a case where an object is in the journal but not + // in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for + // nil + if object, exist := self.stateObjects[addr]; exist { + state.stateObjects[addr] = object.deepCopy(state) + state.stateObjectsDirty[addr] = struct{}{} + } + } + // Above, we don't copy the actual journal. This means that if the copy is copied, the + // loop above will be a no-op, since the copy's journal is empty. + // Thus, here we iterate over stateObjects, to enable copies of copies for addr := range self.stateObjectsDirty { - state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state, state.MarkStateObjectDirty) - state.stateObjectsDirty[addr] = struct{}{} + if _, exist := state.stateObjects[addr]; !exist { + state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state) + state.stateObjectsDirty[addr] = struct{}{} + } } + for hash, logs := range self.logs { state.logs[hash] = make([]*types.Log, len(logs)) copy(state.logs[hash], logs) @@ -492,7 +504,7 @@ func (self *StateDB) Copy() *StateDB { func (self *StateDB) Snapshot() int { id := self.nextRevisionId self.nextRevisionId++ - self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)}) + self.validRevisions = append(self.validRevisions, revision{id, self.journal.length()}) return id } @@ -507,13 +519,8 @@ func (self *StateDB) RevertToSnapshot(revid int) { } snapshot := self.validRevisions[idx].journalIndex - // Replay the journal to undo changes. - for i := len(self.journal) - 1; i >= snapshot; i-- { - self.journal[i].undo(self) - } - self.journal = self.journal[:snapshot] - - // Remove invalidated snapshots from the stack. + // Replay the journal to undo changes and remove invalidated snapshots + self.journal.revert(self, snapshot) self.validRevisions = self.validRevisions[:idx] } @@ -525,14 +532,25 @@ func (self *StateDB) GetRefund() uint64 { // Finalise finalises the state by removing the self destructed objects // and clears the journal as well as the refunds. func (s *StateDB) Finalise(deleteEmptyObjects bool) { - for addr := range s.stateObjectsDirty { - stateObject := s.stateObjects[addr] + for addr := range s.journal.dirties { + stateObject, exist := s.stateObjects[addr] + if !exist { + // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 + // That tx goes out of gas, and although the notion of 'touched' does not exist there, the + // touch-event will still be recorded in the journal. Since ripeMD is a special snowflake, + // it will persist in the journal even though the journal is reverted. In this special circumstance, + // it may exist in `s.journal.dirties` but not in `s.stateObjects`. + // Thus, we can safely ignore it here + continue + } + if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) { s.deleteStateObject(stateObject) } else { stateObject.updateRoot(s.db) s.updateStateObject(stateObject) } + s.stateObjectsDirty[addr] = struct{}{} } // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() @@ -554,29 +572,8 @@ func (self *StateDB) Prepare(thash, bhash common.Hash, ti int) { self.txIndex = ti } -// DeleteSuicides flags the suicided objects for deletion so that it -// won't be referenced again when called / queried up on. -// -// DeleteSuicides should not be used for consensus related updates -// under any circumstances. -func (s *StateDB) DeleteSuicides() { - // Reset refund so that any used-gas calculations can use this method. - s.clearJournalAndRefund() - - for addr := range s.stateObjectsDirty { - stateObject := s.stateObjects[addr] - - // If the object has been removed by a suicide - // flag the object as deleted. - if stateObject.suicided { - stateObject.deleted = true - } - delete(s.stateObjectsDirty, addr) - } -} - func (s *StateDB) clearJournalAndRefund() { - s.journal = nil + s.journal = newJournal() s.validRevisions = s.validRevisions[:0] s.refund = 0 } @@ -585,6 +582,9 @@ func (s *StateDB) clearJournalAndRefund() { func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) { defer s.clearJournalAndRefund() + for addr := range s.journal.dirties { + s.stateObjectsDirty[addr] = struct{}{} + } // Commit objects to the trie. for addr, stateObject := range s.stateObjects { _, isDirty := s.stateObjectsDirty[addr] diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index d9e3d9b7970f..420ca745ccc4 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -413,11 +413,28 @@ func (s *StateSuite) TestTouchDelete(c *check.C) { snapshot := s.state.Snapshot() s.state.AddBalance(common.Address{}, new(big.Int)) - if len(s.state.stateObjectsDirty) != 1 { + + if len(s.state.journal.dirties) != 1 { c.Fatal("expected one dirty state object") } s.state.RevertToSnapshot(snapshot) - if len(s.state.stateObjectsDirty) != 0 { + if len(s.state.journal.dirties) != 0 { c.Fatal("expected no dirty state object") } } + +// TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. +// See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 +func TestCopyOfCopy(t *testing.T) { + db, _ := ethdb.NewMemDatabase() + sdb, _ := New(common.Hash{}, NewDatabase(db)) + addr := common.HexToAddress("aaaa") + sdb.SetBalance(addr, big.NewInt(42)) + + if got := sdb.Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("1st copy fail, expected 42, got %v", got) + } + if got := sdb.Copy().Copy().GetBalance(addr).Uint64(); got != 42 { + t.Fatalf("2nd copy fail, expected 42, got %v", got) + } +} diff --git a/core/state_transition.go b/core/state_transition.go index b19bc12e426e..5654cd01eaae 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -132,28 +132,12 @@ func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, return NewStateTransition(evm, msg, gp).TransitionDb() } -func (st *StateTransition) from() vm.AccountRef { - f := st.msg.From() - if !st.state.Exist(f) { - st.state.CreateAccount(f) +// to returns the recipient of the message. +func (st *StateTransition) to() common.Address { + if st.msg == nil || st.msg.To() == nil /* contract creation */ { + return common.Address{} } - return vm.AccountRef(f) -} - -func (st *StateTransition) to() vm.AccountRef { - if st.msg == nil { - return vm.AccountRef{} - } - to := st.msg.To() - if to == nil { - return vm.AccountRef{} // contract creation - } - - reference := vm.AccountRef(*to) - if !st.state.Exist(*to) { - st.state.CreateAccount(*to) - } - return reference + return *st.msg.To() } func (st *StateTransition) useGas(amount uint64) error { @@ -166,12 +150,8 @@ func (st *StateTransition) useGas(amount uint64) error { } func (st *StateTransition) buyGas() error { - var ( - state = st.state - sender = st.from() - ) mgval := new(big.Int).Mul(new(big.Int).SetUint64(st.msg.Gas()), st.gasPrice) - if state.GetBalance(sender.Address()).Cmp(mgval) < 0 { + if st.state.GetBalance(st.msg.From()).Cmp(mgval) < 0 { return errInsufficientBalanceForGas } if err := st.gp.SubGas(st.msg.Gas()); err != nil { @@ -180,20 +160,17 @@ func (st *StateTransition) buyGas() error { st.gas += st.msg.Gas() st.initialGas = st.msg.Gas() - state.SubBalance(sender.Address(), mgval) + st.state.SubBalance(st.msg.From(), mgval) return nil } func (st *StateTransition) preCheck() error { - msg := st.msg - sender := st.from() - - // Make sure this transaction's nonce is correct - if msg.CheckNonce() { - nonce := st.state.GetNonce(sender.Address()) - if nonce < msg.Nonce() { + // Make sure this transaction's nonce is correct. + if st.msg.CheckNonce() { + nonce := st.state.GetNonce(st.msg.From()) + if nonce < st.msg.Nonce() { return ErrNonceTooHigh - } else if nonce > msg.Nonce() { + } else if nonce > st.msg.Nonce() { return ErrNonceTooLow } } @@ -208,8 +185,7 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo return } msg := st.msg - sender := st.from() // err checked in preCheck - + sender := vm.AccountRef(msg.From()) homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber) contractCreation := msg.To() == nil @@ -233,8 +209,8 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) } else { // Increment the nonce for the next transaction - st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1) - ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value) + st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) + ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) } if vmerr != nil { log.Debug("VM returned with error", "err", vmerr) @@ -260,10 +236,8 @@ func (st *StateTransition) refundGas() { st.gas += refund // Return ETH for remaining gas, exchanged at the original rate. - sender := st.from() - remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) - st.state.AddBalance(sender.Address(), remaining) + st.state.AddBalance(st.msg.From(), remaining) // Also return remaining gas to the block gas counter so it is // available for the next transaction. diff --git a/core/tx_list.go b/core/tx_list.go index 55fc42617d2c..ea6ee7019fc9 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -367,9 +367,20 @@ func (l *txList) Flatten() types.Transactions { // price-sorted transactions to discard when the pool fills up. type priceHeap []*types.Transaction -func (h priceHeap) Len() int { return len(h) } -func (h priceHeap) Less(i, j int) bool { return h[i].GasPrice().Cmp(h[j].GasPrice()) < 0 } -func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h priceHeap) Len() int { return len(h) } +func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } + +func (h priceHeap) Less(i, j int) bool { + // Sort primarily by price, returning the cheaper one + switch h[i].GasPrice().Cmp(h[j].GasPrice()) { + case -1: + return true + case 1: + return false + } + // If the prices match, stabilize via nonces (high nonce is worse) + return h[i].Nonce() > h[j].Nonce() +} func (h *priceHeap) Push(x interface{}) { *h = append(*h, x.(*types.Transaction)) diff --git a/core/tx_pool.go b/core/tx_pool.go index 089bd215ad50..388b40058f1f 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -38,8 +38,6 @@ import ( const ( // chainHeadChanSize is the size of channel listening to ChainHeadEvent. chainHeadChanSize = 10 - // rmTxChanSize is the size of channel listening to RemovedTransactionEvent. - rmTxChanSize = 10 ) var ( @@ -320,7 +318,7 @@ func (pool *TxPool) loop() { // Any non-locals old enough should be removed if time.Since(pool.beats[addr]) > pool.config.Lifetime { for _, tx := range pool.queue[addr].Flatten() { - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), true) } } } @@ -468,7 +466,7 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { pool.gasPrice = price for _, tx := range pool.priced.Cap(price, pool.locals) { - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), false) } log.Info("Transaction pool price threshold updated", "price", price) } @@ -620,7 +618,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { // If the transaction pool is full, discard underpriced transactions if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue { // If the new transaction is underpriced, don't accept it - if pool.priced.Underpriced(tx, pool.locals) { + if !local && pool.priced.Underpriced(tx, pool.locals) { log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) underpricedTxCounter.Inc(1) return false, ErrUnderpriced @@ -630,7 +628,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) { for _, tx := range drop { log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) underpricedTxCounter.Inc(1) - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), false) } } // If the transaction is replacing an already pending one, do directly @@ -695,8 +693,10 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, er pool.priced.Removed() queuedReplaceCounter.Inc(1) } - pool.all[hash] = tx - pool.priced.Put(tx) + if pool.all[hash] == nil { + pool.all[hash] = tx + pool.priced.Put(tx) + } return old != nil, nil } @@ -862,7 +862,7 @@ func (pool *TxPool) Get(hash common.Hash) *types.Transaction { // removeTx removes a single transaction from the queue, moving all subsequent // transactions back to the future queue. -func (pool *TxPool) removeTx(hash common.Hash) { +func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) { // Fetch the transaction we wish to delete tx, ok := pool.all[hash] if !ok { @@ -872,8 +872,9 @@ func (pool *TxPool) removeTx(hash common.Hash) { // Remove it from the list of known transactions delete(pool.all, hash) - pool.priced.Removed() - + if outofbound { + pool.priced.Removed() + } // Remove the transaction from the pending lists and reset the account nonce if pending := pool.pending[addr]; pending != nil { if removed, invalids := pending.Remove(tx); removed { @@ -1052,7 +1053,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) { // Drop all transactions if they are less than the overflow if size := uint64(list.Len()); size <= drop { for _, tx := range list.Flatten() { - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), true) } drop -= size queuedRateLimitCounter.Inc(int64(size)) @@ -1061,7 +1062,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) { // Otherwise drop only last few transactions txs := list.Flatten() for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - pool.removeTx(txs[i].Hash()) + pool.removeTx(txs[i].Hash(), true) drop-- queuedRateLimitCounter.Inc(1) } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 1cf533aa6579..f0f86415d4a4 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -209,15 +209,10 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { pool.lockedReset(nil, nil) - pendingTx, err := pool.Pending() + _, err := pool.Pending() if err != nil { t.Fatalf("Could not fetch pending transactions: %v", err) } - - for addr, txs := range pendingTx { - t.Logf("%0x: %d\n", addr, len(txs)) - } - nonce = pool.State().GetNonce(address) if nonce != 2 { t.Fatalf("Invalid nonce, want 2, got %d", nonce) @@ -350,7 +345,7 @@ func TestTransactionChainFork(t *testing.T) { if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } - pool.removeTx(tx.Hash()) + pool.removeTx(tx.Hash(), true) // reset the pool's internal state resetState() @@ -1351,7 +1346,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) { defer sub.Unsubscribe() // Create a number of test accounts and fund them - keys := make([]*ecdsa.PrivateKey, 3) + keys := make([]*ecdsa.PrivateKey, 4) for i := 0; i < len(keys); i++ { keys[i], _ = crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) @@ -1388,13 +1383,13 @@ func TestTransactionPoolUnderpricing(t *testing.T) { t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) } // Ensure that adding high priced transactions drops cheap ones, but not own - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - t.Fatalf("failed to add well priced transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { + if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 t.Fatalf("failed to add well priced transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { + if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } pending, queued = pool.Stats() @@ -1404,25 +1399,29 @@ func TestTransactionPoolUnderpricing(t *testing.T) { if queued != 2 { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } - if err := validateEvents(events, 2); err != nil { + if err := validateEvents(events, 1); err != nil { t.Fatalf("additional event firing failed: %v", err) } if err := validateTxPoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } // Ensure that adding local transactions can push out even higher priced ones - tx := pricedTransaction(1, 100000, big.NewInt(0), keys[2]) - if err := pool.AddLocal(tx); err != nil { - t.Fatalf("failed to add underpriced local transaction: %v", err) + ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2]) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to append underpriced local transaction: %v", err) + } + ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) + if err := pool.AddLocal(ltx); err != nil { + t.Fatalf("failed to add new underpriced local transaction: %v", err) } pending, queued = pool.Stats() - if pending != 2 { - t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) + if pending != 3 { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3) } - if queued != 2 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) + if queued != 1 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 1) } - if err := validateEvents(events, 1); err != nil { + if err := validateEvents(events, 2); err != nil { t.Fatalf("local event firing failed: %v", err) } if err := validateTxPoolInternals(pool); err != nil { @@ -1430,6 +1429,74 @@ func TestTransactionPoolUnderpricing(t *testing.T) { } } +// Tests that more expensive transactions push out cheap ones from the pool, but +// without producing instability by creating gaps that start jumping transactions +// back and forth between queued/pending. +func TestTransactionPoolStableUnderpricing(t *testing.T) { + t.Parallel() + + // Create the pool to test the pricing enforcement with + db, _ := ethdb.NewMemDatabase() + statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} + + config := testTxPoolConfig + config.GlobalSlots = 128 + config.GlobalQueue = 0 + + pool := NewTxPool(config, params.TestChainConfig, blockchain) + defer pool.Stop() + + // Keep track of transaction events to ensure all executables get announced + events := make(chan TxPreEvent, 32) + sub := pool.txFeed.Subscribe(events) + defer sub.Unsubscribe() + + // Create a number of test accounts and fund them + keys := make([]*ecdsa.PrivateKey, 2) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000)) + } + // Fill up the entire queue with the same transaction price points + txs := types.Transactions{} + for i := uint64(0); i < config.GlobalSlots; i++ { + txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0])) + } + pool.AddRemotes(txs) + + pending, queued := pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, int(config.GlobalSlots)); err != nil { + t.Fatalf("original event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } + // Ensure that adding high priced transactions drops a cheap, but doesn't produce a gap + if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { + t.Fatalf("failed to add well priced transaction: %v", err) + } + pending, queued = pool.Stats() + if pending != int(config.GlobalSlots) { + t.Fatalf("pending transactions mismatched: have %d, want %d", pending, config.GlobalSlots) + } + if queued != 0 { + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 0) + } + if err := validateEvents(events, 1); err != nil { + t.Fatalf("additional event firing failed: %v", err) + } + if err := validateTxPoolInternals(pool); err != nil { + t.Fatalf("pool internal state corrupted: %v", err) + } +} + // Tests that the pool rejects replacement transactions that don't meet the minimum // price bump required. func TestTransactionReplacement(t *testing.T) { diff --git a/core/types/block.go b/core/types/block.go index 92b868d9da7f..ae1b4299d373 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -19,7 +19,6 @@ package types import ( "encoding/binary" - "fmt" "io" "math/big" "sort" @@ -389,40 +388,6 @@ func (b *Block) Hash() common.Hash { return v } -func (b *Block) String() string { - str := fmt.Sprintf(`Block(#%v): Size: %v { -MinerHash: %x -%v -Transactions: -%v -Uncles: -%v -} -`, b.Number(), b.Size(), b.header.HashNoNonce(), b.header, b.transactions, b.uncles) - return str -} - -func (h *Header) String() string { - return fmt.Sprintf(`Header(%x): -[ - ParentHash: %x - UncleHash: %x - Coinbase: %x - Root: %x - TxSha %x - ReceiptSha: %x - Bloom: %x - Difficulty: %v - Number: %v - GasLimit: %v - GasUsed: %v - Time: %v - Extra: %s - MixDigest: %x - Nonce: %x -]`, h.Hash(), h.ParentHash, h.UncleHash, h.Coinbase, h.Root, h.TxHash, h.ReceiptHash, h.Bloom, h.Difficulty, h.Number, h.GasLimit, h.GasUsed, h.Time, h.Extra, h.MixDigest, h.Nonce) -} - type Blocks []*Block type BlockBy func(b1, b2 *Block) bool diff --git a/core/types/log.go b/core/types/log.go index be5de38da79f..b629b47ed53b 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -17,7 +17,6 @@ package types import ( - "fmt" "io" "github.com/ethereum/go-ethereum/common" @@ -95,10 +94,6 @@ func (l *Log) DecodeRLP(s *rlp.Stream) error { return err } -func (l *Log) String() string { - return fmt.Sprintf(`log: %x %x %x %x %d %x %d`, l.Address, l.Topics, l.Data, l.TxHash, l.TxIndex, l.BlockHash, l.Index) -} - // LogForStorage is a wrapper around a Log that flattens and parses the entire content of // a log including non-consensus fields. type LogForStorage Log diff --git a/core/types/receipt.go b/core/types/receipt.go index f945f6f6a21b..613f03d50f51 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -149,14 +149,6 @@ func (r *Receipt) Size() common.StorageSize { return size } -// String implements the Stringer interface. -func (r *Receipt) String() string { - if len(r.PostState) == 0 { - return fmt.Sprintf("receipt{status=%d cgas=%v bloom=%x logs=%v}", r.Status, r.CumulativeGasUsed, r.Bloom, r.Logs) - } - return fmt.Sprintf("receipt{med=%x cgas=%v bloom=%x logs=%v}", r.PostState, r.CumulativeGasUsed, r.Bloom, r.Logs) -} - // ReceiptForStorage is a wrapper around a Receipt that flattens and parses the // entire content of a receipt, as opposed to only the consensus fields originally. type ReceiptForStorage Receipt diff --git a/core/types/transaction.go b/core/types/transaction.go index 5660582baf16..c1cb7a043ad2 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -19,7 +19,6 @@ package types import ( "container/heap" "errors" - "fmt" "io" "math/big" "sync/atomic" @@ -34,7 +33,6 @@ import ( var ( ErrInvalidSig = errors.New("invalid transaction v, r, s values") - errNoSigner = errors.New("missing signing methods") ) // deriveSigner makes a *best* guess about which signer to use. @@ -262,58 +260,6 @@ func (tx *Transaction) RawSignatureValues() (*big.Int, *big.Int, *big.Int) { return tx.data.V, tx.data.R, tx.data.S } -func (tx *Transaction) String() string { - var from, to string - if tx.data.V != nil { - // make a best guess about the signer and use that to derive - // the sender. - signer := deriveSigner(tx.data.V) - if f, err := Sender(signer, tx); err != nil { // derive but don't cache - from = "[invalid sender: invalid sig]" - } else { - from = fmt.Sprintf("%x", f[:]) - } - } else { - from = "[invalid sender: nil V field]" - } - - if tx.data.Recipient == nil { - to = "[contract creation]" - } else { - to = fmt.Sprintf("%x", tx.data.Recipient[:]) - } - enc, _ := rlp.EncodeToBytes(&tx.data) - return fmt.Sprintf(` - TX(%x) - Contract: %v - From: %s - To: %s - Nonce: %v - GasPrice: %#x - GasLimit %#x - Value: %#x - Data: 0x%x - V: %#x - R: %#x - S: %#x - Hex: %x -`, - tx.Hash(), - tx.data.Recipient == nil, - from, - to, - tx.data.AccountNonce, - tx.data.Price, - tx.data.GasLimit, - tx.data.Amount, - tx.data.Payload, - tx.data.V, - tx.data.R, - tx.data.S, - enc, - ) -} - // Transactions is a Transaction slice type for basic sorting. type Transactions []*Transaction @@ -393,11 +339,14 @@ type TransactionsByPriceAndNonce struct { func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions) *TransactionsByPriceAndNonce { // Initialize a price based heap with the head transactions heads := make(TxByPrice, 0, len(txs)) - for _, accTxs := range txs { + for from, accTxs := range txs { heads = append(heads, accTxs[0]) // Ensure the sender address is from the signer acc, _ := Sender(signer, accTxs[0]) txs[acc] = accTxs[1:] + if from != acc { + delete(txs, from) + } } heap.Init(&heads) diff --git a/core/vm/contract.go b/core/vm/contract.go index 66748e821589..b466681dbd67 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -139,15 +139,15 @@ func (c *Contract) Value() *big.Int { } // SetCode sets the code to the contract -func (self *Contract) SetCode(hash common.Hash, code []byte) { - self.Code = code - self.CodeHash = hash +func (c *Contract) SetCode(hash common.Hash, code []byte) { + c.Code = code + c.CodeHash = hash } // SetCallCode sets the code of the contract and address of the backing data // object -func (self *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { - self.Code = code - self.CodeHash = hash - self.CodeAddr = addr +func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) { + c.Code = code + c.CodeHash = hash + c.CodeAddr = addr } diff --git a/core/vm/evm.go b/core/vm/evm.go index 96676c314a40..ea4620974233 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -160,6 +160,11 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas precompiles = PrecompiledContractsByzantium } if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 { + // Calling a non existing account, don't do antything, but ping the tracer + if evm.vmConfig.Debug && evm.depth == 0 { + evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) + evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) + } return nil, gas, nil } evm.StateDB.CreateAccount(addr) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 1e494a0eb89e..0689ee39cf34 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -31,7 +31,6 @@ import ( var ( bigZero = new(big.Int) tt255 = math.BigPow(2, 255) - tt256 = math.BigPow(2, 256) errWriteProtection = errors.New("evm: write protection") errReturnDataOutOfBounds = errors.New("evm: return data out of bounds") errExecutionReverted = errors.New("evm: execution reverted") diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 47d5e7f2fa02..7090e0261fc0 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -42,7 +42,7 @@ type Config struct { } // Interpreter is used to run Ethereum based contracts and will utilise the -// passed evmironment to query external sources for state information. +// passed environment to query external sources for state information. // The Interpreter will run the byte code VM based on the passed // configuration. type Interpreter struct { @@ -184,7 +184,7 @@ func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err er } } // consume the gas and return an error if not enough gas is available. - // cost is explicitly set so that the capture state defer method cas get the proper cost + // cost is explicitly set so that the capture state defer method can get the proper cost cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize) if err != nil || !contract.UseGas(cost) { return nil, ErrOutOfGas diff --git a/core/vm/logger.go b/core/vm/logger.go index 4c820d8b5331..c32a7b4044e8 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -31,9 +31,9 @@ import ( type Storage map[common.Hash]common.Hash -func (self Storage) Copy() Storage { +func (s Storage) Copy() Storage { cpy := make(Storage) - for key, value := range self { + for key, value := range s { cpy[key] = value } @@ -45,6 +45,7 @@ type LogConfig struct { DisableMemory bool // disable memory capture DisableStack bool // disable stack capture DisableStorage bool // disable storage capture + Debug bool // print output during capture end Limit int // maximum length of output, but zero means unlimited } @@ -184,6 +185,12 @@ func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost ui func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { l.output = output l.err = err + if l.cfg.Debug { + fmt.Printf("0x%x\n", output) + if err != nil { + fmt.Printf(" error: %v\n", err) + } + } return nil } diff --git a/core/vm/memory.go b/core/vm/memory.go index 99a84d227158..d5cc7870bc77 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -51,14 +51,14 @@ func (m *Memory) Resize(size uint64) { } // Get returns offset + size as a new slice -func (self *Memory) Get(offset, size int64) (cpy []byte) { +func (m *Memory) Get(offset, size int64) (cpy []byte) { if size == 0 { return nil } - if len(self.store) > int(offset) { + if len(m.store) > int(offset) { cpy = make([]byte, size) - copy(cpy, self.store[offset:offset+size]) + copy(cpy, m.store[offset:offset+size]) return } @@ -67,13 +67,13 @@ func (self *Memory) Get(offset, size int64) (cpy []byte) { } // GetPtr returns the offset + size -func (self *Memory) GetPtr(offset, size int64) []byte { +func (m *Memory) GetPtr(offset, size int64) []byte { if size == 0 { return nil } - if len(self.store) > int(offset) { - return self.store[offset : offset+size] + if len(m.store) > int(offset) { + return m.store[offset : offset+size] } return nil diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 7fe55b72f6a2..e3568eb000a3 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -375,10 +375,10 @@ var opCodeToString = map[OpCode]string{ SWAP: "SWAP", } -func (o OpCode) String() string { - str := opCodeToString[o] +func (op OpCode) String() string { + str := opCodeToString[op] if len(str) == 0 { - return fmt.Sprintf("Missing opcode 0x%x", int(o)) + return fmt.Sprintf("Missing opcode 0x%x", int(op)) } return str diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 1e9ed7ae2db1..5ac5464064ad 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -103,7 +103,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(db)) } var ( - address = common.StringToAddress("contract") + address = common.BytesToAddress([]byte("contract")) vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) ) @@ -113,7 +113,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // Call the code with the given configuration. ret, _, err := vmenv.Call( sender, - common.StringToAddress("contract"), + common.BytesToAddress([]byte("contract")), input, cfg.GasLimit, cfg.Value, diff --git a/crypto/bn256/cloudflare/example_test.go b/crypto/bn256/cloudflare/example_test.go index b2d19807a25e..6c285995cb22 100644 --- a/crypto/bn256/cloudflare/example_test.go +++ b/crypto/bn256/cloudflare/example_test.go @@ -6,9 +6,12 @@ package bn256 import ( "crypto/rand" + "testing" + + "github.com/stretchr/testify/require" ) -func ExamplePair() { +func TestExamplePair(t *testing.T) { // This implements the tripartite Diffie-Hellman algorithm from "A One // Round Protocol for Tripartite Diffie-Hellman", A. Joux. // http://www.springerlink.com/content/cddc57yyva0hburb/fulltext.pdf @@ -40,4 +43,9 @@ func ExamplePair() { k3.ScalarMult(k3, c) // k1, k2 and k3 will all be equal. + + require.Equal(t, k1, k2) + require.Equal(t, k1, k3) + + require.Equal(t, len(np), 4) //Avoid gometalinter varcheck err on np } diff --git a/crypto/secp256k1/curve.go b/crypto/secp256k1/curve.go index df804818576e..f51be5e35301 100644 --- a/crypto/secp256k1/curve.go +++ b/crypto/secp256k1/curve.go @@ -290,11 +290,11 @@ func init() { // See SEC 2 section 2.7.1 // curve parameters taken from: // http://www.secg.org/collateral/sec2_final.pdf - theCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16) - theCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) - theCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16) - theCurve.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16) - theCurve.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16) + theCurve.P = math.MustParseBig256("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F") + theCurve.N = math.MustParseBig256("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141") + theCurve.B = math.MustParseBig256("0x0000000000000000000000000000000000000000000000000000000000000007") + theCurve.Gx = math.MustParseBig256("0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798") + theCurve.Gy = math.MustParseBig256("0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8") theCurve.BitSize = 256 } diff --git a/eth/api.go b/eth/api.go index a345b57e498f..adfb7f9f0b70 100644 --- a/eth/api.go +++ b/eth/api.go @@ -24,9 +24,11 @@ import ( "math/big" "os" "strings" + "errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -210,6 +212,20 @@ func NewPrivateAdminAPI(eth *Ethereum) *PrivateAdminAPI { return &PrivateAdminAPI{eth: eth} } +// CompileSolidity compiles the given solidity source +func (s *PublicEthereumAPI) CompileSolidity(source string) (map[string]*compiler.Contract, error) { + solc, err := s.e.Solc() + if err != nil { + return nil, err + } + + if solc == nil { + return nil, errors.New("solc (solidity compiler) not found") + } + + return solc.Compile(source) +} + // ExportChain exports the current blockchain into a local file. func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { // Make sure we can create the file to export into diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 07c4457bc398..80a3ab719d0b 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -201,7 +201,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) break } - task.statedb.DeleteSuicides() + task.statedb.Finalise(true) task.results[i] = &txTraceResult{Result: res} } // Stream the result back to the user or abort on teardown @@ -640,7 +640,8 @@ func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, ree if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) } - statedb.DeleteSuicides() + // Ensure any modifications are committed to the state + statedb.Finalise(true) } return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash) } diff --git a/eth/backend.go b/eth/backend.go index 94aad23101b8..501fc731457d 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -63,7 +64,7 @@ type Ethereum struct { chainConfig *params.ChainConfig // Channel for shutting down the service - shutdownChan chan bool // Channel for shutting down the ethereum + shutdownChan chan bool // Channel for shutting down the Ethereum stopDbUpgrade func() error // stop chain db sequential key upgrade // Handlers @@ -79,6 +80,8 @@ type Ethereum struct { engine consensus.Engine accountManager *accounts.Manager + solc *compiler.Solidity + bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -334,6 +337,14 @@ func (self *Ethereum) SetEtherbase(etherbase common.Address) { self.miner.SetEtherbase(etherbase) } +func (self *Ethereum) Solc() (*compiler.Solidity, error) { + var err error + if self.solc == nil { + self.solc, err = compiler.New("") + } + return self.solc, err +} + func (s *Ethereum) StartMining(local bool) error { eb, err := s.Etherbase() if err != nil { @@ -351,7 +362,7 @@ func (s *Ethereum) StartMining(local bool) error { if local { // If local (CPU) mining is started, we can disable the transaction rejection // mechanism introduced to speed sync times. CPU mining on mainnet is ludicrous - // so noone will ever hit this path, whereas marking sync done on CPU mining + // so none will ever hit this path, whereas marking sync done on CPU mining // will ensure that private networks work in single miner mode too. atomic.StoreUint32(&s.protocolManager.acceptTxs, 1) } diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index d41afa17c057..96c584ac64a0 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -62,7 +62,7 @@ func upgradeDeduplicateData(db ethdb.Database) func() error { failed error ) for failed == nil && it.Next() { - // Skip any entries that don't look like old transaction meta entires (0x01) + // Skip any entries that don't look like old transaction meta entries (0x01) key := it.Key() if len(key) != common.HashLength+1 || key[common.HashLength] != 0x01 { continue @@ -86,7 +86,7 @@ func upgradeDeduplicateData(db ethdb.Database) func() error { } } // Convert the old metadata to a new lookup entry, delete duplicate data - if failed = db.Put(append([]byte("l"), hash...), it.Value()); failed == nil { // Write the new looku entry + if failed = db.Put(append([]byte("l"), hash...), it.Value()); failed == nil { // Write the new lookup entry if failed = db.Delete(hash); failed == nil { // Delete the duplicate transaction data if failed = db.Delete(append([]byte("receipts-"), hash...)); failed == nil { // Delete the duplicate receipt data if failed = db.Delete(key); failed != nil { // Delete the old transaction metadata diff --git a/eth/downloader/api.go b/eth/downloader/api.go index d496fa6a4d4f..91c6322d41db 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -51,7 +51,7 @@ func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAP return api } -// eventLoop runs an loop until the event mux closes. It will install and uninstall new +// eventLoop runs a loop until the event mux closes. It will install and uninstall new // sync subscriptions and broadcasts sync status updates to the installed sync subscriptions. func (api *PublicDownloaderAPI) eventLoop() { var ( diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 62842adbc6be..43f0e3db977c 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -47,7 +47,7 @@ var ( MaxForkAncestry = 3 * params.EpochDuration // Maximum chain reorganisation rttMinEstimate = 2 * time.Second // Minimum round-trip time to target for download requests - rttMaxEstimate = 20 * time.Second // Maximum rount-trip time to target for download requests + rttMaxEstimate = 20 * time.Second // Maximum round-trip time to target for download requests rttMinConfidence = 0.1 // Worse confidence factor in our estimated RTT value ttlScaling = 3 // Constant scaling factor for RTT -> TTL conversion ttlLimit = time.Minute // Maximum TTL allowance to prevent reaching crazy timeouts @@ -135,9 +135,10 @@ type Downloader struct { stateCh chan dataPack // [eth/63] Channel receiving inbound node state data // Cancellation and termination - cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) - cancelCh chan struct{} // Channel to cancel mid-flight syncs - cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers + cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) + cancelCh chan struct{} // Channel to cancel mid-flight syncs + cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers + cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. quitCh chan struct{} // Quit channel to signal termination quitLock sync.RWMutex // Lock to prevent double closes @@ -305,7 +306,7 @@ func (d *Downloader) UnregisterPeer(id string) error { d.cancelLock.RUnlock() if master { - d.Cancel() + d.cancel() } return nil } @@ -476,12 +477,11 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.I // spawnSync runs d.process and all given fetcher functions to completion in // separate goroutines, returning the first error that appears. func (d *Downloader) spawnSync(fetchers []func() error) error { - var wg sync.WaitGroup errc := make(chan error, len(fetchers)) - wg.Add(len(fetchers)) + d.cancelWg.Add(len(fetchers)) for _, fn := range fetchers { fn := fn - go func() { defer wg.Done(); errc <- fn() }() + go func() { defer d.cancelWg.Done(); errc <- fn() }() } // Wait for the first error, then terminate the others. var err error @@ -498,13 +498,13 @@ func (d *Downloader) spawnSync(fetchers []func() error) error { } d.queue.Close() d.Cancel() - wg.Wait() return err } -// Cancel cancels all of the operations and resets the queue. It returns true -// if the cancel operation was completed. -func (d *Downloader) Cancel() { +// cancel aborts all of the operations and resets the queue. However, cancel does +// not wait for the running download goroutines to finish. This method should be +// used when cancelling the downloads from inside the downloader. +func (d *Downloader) cancel() { // Close the current cancel channel d.cancelLock.Lock() if d.cancelCh != nil { @@ -518,6 +518,13 @@ func (d *Downloader) Cancel() { d.cancelLock.Unlock() } +// Cancel aborts all of the operations and waits for all download goroutines to +// finish before returning. +func (d *Downloader) Cancel() { + d.cancel() + d.cancelWg.Wait() +} + // Terminate interrupts the downloader, canceling all pending operations. // The downloader cannot be reused after calling Terminate. func (d *Downloader) Terminate() { @@ -884,7 +891,7 @@ func (d *Downloader) fetchHeaders(p *peerConnection, from uint64, pivot uint64) // immediately to the header processor to keep the rest of the pipeline full even // in the case of header stalls. // -// The method returs the entire filled skeleton and also the number of headers +// The method returns the entire filled skeleton and also the number of headers // already forwarded for processing. func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ([]*types.Header, int, error) { log.Debug("Filling up skeleton", "from", from) @@ -1377,7 +1384,7 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error { pivot = height - uint64(fsMinFullBlocks) } // To cater for moving pivot points, track the pivot block and subsequently - // accumulated download results separatey. + // accumulated download results separately. var ( oldPivot *fetchResult // Locked in pivot block, might change eventually oldTail []*fetchResult // Downloaded content after the pivot @@ -1615,7 +1622,7 @@ func (d *Downloader) qosReduceConfidence() { // // Note, the returned RTT is .9 of the actually estimated RTT. The reason is that // the downloader tries to adapt queries to the RTT, so multiple RTT values can -// be adapted to, but smaller ones are preffered (stabler download stream). +// be adapted to, but smaller ones are preferred (stabler download stream). func (d *Downloader) requestRTT() time.Duration { return time.Duration(atomic.LoadUint64(&d.rttEstimate)) * 9 / 10 } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index cb671a7df46f..e85e234c0eb1 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -159,7 +159,7 @@ func (dl *downloadTester) makeChainFork(n, f int, parent *types.Block, parentRec // Create the common suffix hashes, headers, blocks, receipts := dl.makeChain(n-f, 0, parent, parentReceipts, false) - // Create the forks, making the second heavyer if non balanced forks were requested + // Create the forks, making the second heavier if non balanced forks were requested hashes1, headers1, blocks1, receipts1 := dl.makeChain(f, 1, blocks[hashes[0]], receipts[hashes[0]], false) hashes1 = append(hashes1, hashes[1:]...) diff --git a/eth/downloader/fakepeer.go b/eth/downloader/fakepeer.go index b45acff7d04f..5248e7fb0cd3 100644 --- a/eth/downloader/fakepeer.go +++ b/eth/downloader/fakepeer.go @@ -27,7 +27,7 @@ import ( // FakePeer is a mock downloader peer that operates on a local database instance // instead of being an actual live node. It's useful for testing and to implement -// sync commands from an xisting local database. +// sync commands from an existing local database. type FakePeer struct { id string db ethdb.Database @@ -48,7 +48,7 @@ func (p *FakePeer) Head() (common.Hash, *big.Int) { } // RequestHeadersByHash implements downloader.Peer, returning a batch of headers -// defined by the origin hash and the associaed query parameters. +// defined by the origin hash and the associated query parameters. func (p *FakePeer) RequestHeadersByHash(hash common.Hash, amount int, skip int, reverse bool) error { var ( headers []*types.Header @@ -92,7 +92,7 @@ func (p *FakePeer) RequestHeadersByHash(hash common.Hash, amount int, skip int, } // RequestHeadersByNumber implements downloader.Peer, returning a batch of headers -// defined by the origin number and the associaed query parameters. +// defined by the origin number and the associated query parameters. func (p *FakePeer) RequestHeadersByNumber(number uint64, amount int, skip int, reverse bool) error { var ( headers []*types.Header diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index a4aa861146ad..428a60f8a109 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -551,7 +551,7 @@ func (ps *peerSet) idlePeers(minProtocol, maxProtocol int, idleCheck func(*peerC // medianRTT returns the median RTT of the peerset, considering only the tuning // peers if there are more peers available. func (ps *peerSet) medianRTT() time.Duration { - // Gather all the currnetly measured round trip times + // Gather all the currently measured round trip times ps.lock.RLock() defer ps.lock.RUnlock() diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 359cce54b5c6..bbe0aed5dca6 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -275,7 +275,7 @@ func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) { if q.headerResults != nil { panic("skeleton assembly already in progress") } - // Shedule all the header retrieval tasks for the skeleton assembly + // Schedule all the header retrieval tasks for the skeleton assembly q.headerTaskPool = make(map[uint64]*types.Header) q.headerTaskQueue = prque.New() q.headerPeerMiss = make(map[string]map[uint64]struct{}) // Reset availability to correct invalid chains diff --git a/eth/downloader/statesync.go b/eth/downloader/statesync.go index ee6c7b491401..521ee25a02bd 100644 --- a/eth/downloader/statesync.go +++ b/eth/downloader/statesync.go @@ -31,7 +31,7 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -// stateReq represents a batch of state fetch requests groupped together into +// stateReq represents a batch of state fetch requests grouped together into // a single data retrieval network packet. type stateReq struct { items []common.Hash // Hashes of the state items to download @@ -139,7 +139,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { // Handle incoming state packs: case pack := <-d.stateCh: - // Discard any data not requested (or previsouly timed out) + // Discard any data not requested (or previously timed out) req := active[pack.PeerId()] if req == nil { log.Debug("Unrequested node data", "peer", pack.PeerId(), "len", pack.Items()) @@ -182,7 +182,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync { case req := <-d.trackStateReq: // If an active request already exists for this peer, we have a problem. In // theory the trie node schedule must never assign two requests to the same - // peer. In practive however, a peer might receive a request, disconnect and + // peer. In practice however, a peer might receive a request, disconnect and // immediately reconnect before the previous times out. In this case the first // request is never honored, alas we must not silently overwrite it, as that // causes valid requests to go missing and sync to get stuck. @@ -228,7 +228,7 @@ type stateSync struct { err error // Any error hit during sync (set before completion) } -// stateTask represents a single trie node download taks, containing a set of +// stateTask represents a single trie node download task, containing a set of // peers already attempted retrieval from to detect stalled syncs and abort. type stateTask struct { attempts map[string]struct{} @@ -274,15 +274,21 @@ func (s *stateSync) Cancel() error { // receive data from peers, rather those are buffered up in the downloader and // pushed here async. The reason is to decouple processing from data receipt // and timeouts. -func (s *stateSync) loop() error { +func (s *stateSync) loop() (err error) { // Listen for new peer events to assign tasks to them newPeer := make(chan *peerConnection, 1024) peerSub := s.d.peers.SubscribeNewPeers(newPeer) defer peerSub.Unsubscribe() + defer func() { + cerr := s.commit(true) + if err == nil { + err = cerr + } + }() // Keep assigning new tasks until the sync completes or aborts for s.sched.Pending() > 0 { - if err := s.commit(false); err != nil { + if err = s.commit(false); err != nil { return err } s.assignTasks() @@ -307,14 +313,14 @@ func (s *stateSync) loop() error { s.d.dropPeer(req.peer.id) } // Process all the received blobs and check for stale delivery - if err := s.process(req); err != nil { + if err = s.process(req); err != nil { log.Warn("Node data write error", "err", err) return err } req.peer.SetNodeDataIdle(len(req.response)) } } - return s.commit(true) + return nil } func (s *stateSync) commit(force bool) error { @@ -323,7 +329,9 @@ func (s *stateSync) commit(force bool) error { } start := time.Now() b := s.d.stateDB.NewBatch() - s.sched.Commit(b) + if written, err := s.sched.Commit(b); written == 0 || err != nil { + return err + } if err := b.Write(); err != nil { return fmt.Errorf("DB write error: %v", err) } @@ -333,7 +341,7 @@ func (s *stateSync) commit(force bool) error { return nil } -// assignTasks attempts to assing new tasks to all idle peers, either from the +// assignTasks attempts to assign new tasks to all idle peers, either from the // batch currently being retried, or fetching new data from the trie sync itself. func (s *stateSync) assignTasks() { // Iterate over all idle peers and try to assign them state fetches diff --git a/eth/fetcher/fetcher.go b/eth/fetcher/fetcher.go index db554e1440b1..0c679cec3a78 100644 --- a/eth/fetcher/fetcher.go +++ b/eth/fetcher/fetcher.go @@ -127,7 +127,7 @@ type Fetcher struct { // Block cache queue *prque.Prque // Queue containing the import operations (block number sorted) queues map[string]int // Per peer block counts to prevent memory exhaustion - queued map[common.Hash]*inject // Set of already queued blocks (to dedup imports) + queued map[common.Hash]*inject // Set of already queued blocks (to dedupe imports) // Callbacks getBlock blockRetrievalFn // Retrieves a block from the local chain diff --git a/eth/fetcher/fetcher_test.go b/eth/fetcher/fetcher_test.go index 9d53b98b6036..1e536fcacbeb 100644 --- a/eth/fetcher/fetcher_test.go +++ b/eth/fetcher/fetcher_test.go @@ -198,7 +198,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ } } -// verifyFetchingEvent verifies that one single event arrive on an fetching channel. +// verifyFetchingEvent verifies that one single event arrive on a fetching channel. func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) { if arrive { select { diff --git a/eth/filters/api.go b/eth/filters/api.go index 406c9442e09d..ec403709c587 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -98,7 +98,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes // as transactions enter the pending state. // -// It is part of the filter package because this filter can be used throug the +// It is part of the filter package because this filter can be used through the // `eth_getFilterChanges` polling method that is also used for log filters. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter diff --git a/eth/filters/api_test.go b/eth/filters/api_test.go index 4ae37f977925..02229a7549a7 100644 --- a/eth/filters/api_test.go +++ b/eth/filters/api_test.go @@ -29,8 +29,8 @@ func TestUnmarshalJSONNewFilterArgs(t *testing.T) { var ( fromBlock rpc.BlockNumber = 0x123435 toBlock rpc.BlockNumber = 0xabcdef - address0 = common.StringToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d") - address1 = common.StringToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83") + address0 = common.HexToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d") + address1 = common.HexToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83") topic0 = common.HexToHash("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1ca") topic1 = common.HexToHash("9084a792d2f8b16a62b882fd56f7860c07bf5fa91dd8a2ae7e809e5180fef0b3") topic2 = common.HexToHash("6ccae1c4af4152f460ff510e573399795dfab5dcf1fa60d1f33ac8fdc1e480ce") diff --git a/eth/handler.go b/eth/handler.go index 3fae0cd00d16..4069359c9ecd 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -96,8 +96,8 @@ type ProtocolManager struct { wg sync.WaitGroup } -// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable -// with the ethereum network. +// NewProtocolManager returns a new Ethereum sub protocol manager. The Ethereum sub protocol manages peers capable +// with the Ethereum network. func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) { // Create the protocol manager with the base fields manager := &ProtocolManager{ @@ -498,20 +498,20 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } // Deliver them all to the downloader for queuing - trasactions := make([][]*types.Transaction, len(request)) + transactions := make([][]*types.Transaction, len(request)) uncles := make([][]*types.Header, len(request)) for i, body := range request { - trasactions[i] = body.Transactions + transactions[i] = body.Transactions uncles[i] = body.Uncles } // Filter out any explicitly requested bodies, deliver the rest to the downloader - filter := len(trasactions) > 0 || len(uncles) > 0 + filter := len(transactions) > 0 || len(uncles) > 0 if filter { - trasactions, uncles = pm.fetcher.FilterBodies(p.id, trasactions, uncles, time.Now()) + transactions, uncles = pm.fetcher.FilterBodies(p.id, transactions, uncles, time.Now()) } - if len(trasactions) > 0 || len(uncles) > 0 || !filter { - err := pm.downloader.DeliverBodies(p.id, trasactions, uncles) + if len(transactions) > 0 || len(uncles) > 0 || !filter { + err := pm.downloader.DeliverBodies(p.id, transactions, uncles) if err != nil { log.Debug("Failed to deliver bodies", "err", err) } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 87a912901af5..b48224587861 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -39,7 +39,11 @@ type Client struct { // Dial connects a client to the given URL. func Dial(rawurl string) (*Client, error) { - c, err := rpc.Dial(rawurl) + return DialContext(context.Background(), rawurl) +} + +func DialContext(ctx context.Context, rawurl string) (*Client, error) { + c, err := rpc.DialContext(ctx, rawurl) if err != nil { return nil, err } @@ -51,6 +55,10 @@ func NewClient(c *rpc.Client) *Client { return &Client{c} } +func (ec *Client) Close() { + ec.c.Close() +} + // Blockchain Access // BlockByHash returns the given full block. @@ -296,7 +304,7 @@ func (ec *Client) SyncProgress(ctx context.Context) (*ethereum.SyncProgress, err // SubscribeNewHead subscribes to notifications about the current blockchain head // on the given channel. func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { - return ec.c.EthSubscribe(ctx, ch, "newHeads", map[string]struct{}{}) + return ec.c.EthSubscribe(ctx, ch, "newHeads") } // State Access diff --git a/ethdb/database.go b/ethdb/database.go index 30ed37dc7b64..001d8f0bb943 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -17,6 +17,7 @@ package ethdb import ( + "fmt" "strconv" "strings" "sync" @@ -32,17 +33,25 @@ import ( "github.com/syndtr/goleveldb/leveldb/util" ) +const ( + writeDelayNThreshold = 200 + writeDelayThreshold = 350 * time.Millisecond + writeDelayWarningThrottler = 1 * time.Minute +) + var OpenFileLimit = 64 type LDBDatabase struct { fn string // filename for reporting db *leveldb.DB // LevelDB instance - compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction - compReadMeter metrics.Meter // Meter for measuring the data read during compaction - compWriteMeter metrics.Meter // Meter for measuring the data written during compaction - diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read - diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written + compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction + compReadMeter metrics.Meter // Meter for measuring the data read during compaction + compWriteMeter metrics.Meter // Meter for measuring the data written during compaction + writeDelayNMeter metrics.Meter // Meter for measuring the write delay number due to database compaction + writeDelayMeter metrics.Meter // Meter for measuring the write delay duration due to database compaction + diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read + diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written quitLock sync.Mutex // Mutex protecting the quit channel access quitChan chan chan error // Quit channel to stop the metrics collection before closing the database @@ -91,9 +100,6 @@ func (db *LDBDatabase) Path() string { // Put puts the given key / value to the queue func (db *LDBDatabase) Put(key []byte, value []byte) error { - // Generate the data to write to disk, update the meter and write - //value = rle.Compress(value) - return db.db.Put(key, value, nil) } @@ -103,18 +109,15 @@ func (db *LDBDatabase) Has(key []byte) (bool, error) { // Get returns the given key if it's present. func (db *LDBDatabase) Get(key []byte) ([]byte, error) { - // Retrieve the key and increment the miss counter if not found dat, err := db.db.Get(key, nil) if err != nil { return nil, err } return dat, nil - //return rle.Decompress(dat) } // Delete deletes the key from the queue and database func (db *LDBDatabase) Delete(key []byte) error { - // Execute the actual operation return db.db.Delete(key, nil) } @@ -153,16 +156,17 @@ func (db *LDBDatabase) LDB() *leveldb.DB { // Meter configures the database metrics collectors and func (db *LDBDatabase) Meter(prefix string) { - // Short circuit metering if the metrics system is disabled - if !metrics.Enabled { - return + if metrics.Enabled { + // Initialize all the metrics collector at the requested prefix + db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil) + db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil) + db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil) + db.diskReadMeter = metrics.NewRegisteredMeter(prefix+"disk/read", nil) + db.diskWriteMeter = metrics.NewRegisteredMeter(prefix+"disk/write", nil) } - // Initialize all the metrics collector at the requested prefix - db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil) - db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil) - db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil) - db.diskReadMeter = metrics.NewRegisteredMeter(prefix+"disk/read", nil) - db.diskWriteMeter = metrics.NewRegisteredMeter(prefix+"disk/write", nil) + // Initialize write delay metrics no matter we are in metric mode or not. + db.writeDelayMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/duration", nil) + db.writeDelayNMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/counter", nil) // Create a quit channel for the periodic collector and run it db.quitLock.Lock() @@ -184,6 +188,9 @@ func (db *LDBDatabase) Meter(prefix string) { // 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884 // 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000 // +// This is how the write delay look like (currently): +// DelayN:5 Delay:406.604657ms +// // This is how the iostats look like (currently): // Read(MB):3895.04860 Write(MB):3654.64712 func (db *LDBDatabase) meter(refresh time.Duration) { @@ -194,6 +201,14 @@ func (db *LDBDatabase) meter(refresh time.Duration) { } // Create storage for iostats. var iostats [2]float64 + + // Create storage and warning log tracer for write delay. + var ( + delaystats [2]int64 + lastWriteDelay time.Time + lastWriteDelayN time.Time + ) + // Iterate ad infinitum and collect the stats for i := 1; ; i++ { // Retrieve the database stats @@ -242,6 +257,52 @@ func (db *LDBDatabase) meter(refresh time.Duration) { db.compWriteMeter.Mark(int64((compactions[i%2][2] - compactions[(i-1)%2][2]) * 1024 * 1024)) } + // Retrieve the write delay statistic + writedelay, err := db.db.GetProperty("leveldb.writedelay") + if err != nil { + db.log.Error("Failed to read database write delay statistic", "err", err) + return + } + var ( + delayN int64 + delayDuration string + duration time.Duration + ) + if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s", &delayN, &delayDuration); n != 2 || err != nil { + db.log.Error("Write delay statistic not found") + return + } + duration, err = time.ParseDuration(delayDuration) + if err != nil { + db.log.Error("Failed to parse delay duration", "err", err) + return + } + if db.writeDelayNMeter != nil { + db.writeDelayNMeter.Mark(delayN - delaystats[0]) + // If the write delay number been collected in the last minute exceeds the predefined threshold, + // print a warning log here. + // If a warning that db performance is laggy has been displayed, + // any subsequent warnings will be withhold for 1 minute to don't overwhelm the user. + if int(db.writeDelayNMeter.Rate1()) > writeDelayNThreshold && + time.Now().After(lastWriteDelayN.Add(writeDelayWarningThrottler)) { + db.log.Warn("Write delay number exceeds the threshold (200 per second) in the last minute") + lastWriteDelayN = time.Now() + } + } + if db.writeDelayMeter != nil { + db.writeDelayMeter.Mark(duration.Nanoseconds() - delaystats[1]) + // If the write delay duration been collected in the last minute exceeds the predefined threshold, + // print a warning log here. + // If a warning that db performance is laggy has been displayed, + // any subsequent warnings will be withhold for 1 minute to don't overwhelm the user. + if int64(db.writeDelayMeter.Rate1()) > writeDelayThreshold.Nanoseconds() && + time.Now().After(lastWriteDelay.Add(writeDelayWarningThrottler)) { + db.log.Warn("Write delay duration exceeds the threshold (35% of the time) in the last minute") + lastWriteDelay = time.Now() + } + } + delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() + // Retrieve the database iostats. ioStats, err := db.db.GetProperty("leveldb.iostats") if err != nil { diff --git a/internal/debug/flags.go b/internal/debug/flags.go index 1f181bf8b0f0..5eb58e9eef1c 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -28,10 +28,13 @@ import ( "github.com/ethereum/go-ethereum/log/term" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" + "github.com/fjl/memsize/memsizeui" colorable "github.com/mattn/go-colorable" "gopkg.in/urfave/cli.v1" ) +var Memsize memsizeui.Handler + var ( verbosityFlag = cli.IntFlag{ Name: "verbosity", @@ -129,21 +132,25 @@ func Setup(ctx *cli.Context) error { // pprof server if ctx.GlobalBool(pprofFlag.Name) { - // Hook go-metrics into expvar on any /debug/metrics request, load all vars - // from the registry into expvar, and execute regular expvar handler. - exp.Exp(metrics.DefaultRegistry) - address := fmt.Sprintf("%s:%d", ctx.GlobalString(pprofAddrFlag.Name), ctx.GlobalInt(pprofPortFlag.Name)) - go func() { - log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) - if err := http.ListenAndServe(address, nil); err != nil { - log.Error("Failure in running pprof server", "err", err) - } - }() + StartPProf(address) } return nil } +func StartPProf(address string) { + // Hook go-metrics into expvar on any /debug/metrics request, load all vars + // from the registry into expvar, and execute regular expvar handler. + exp.Exp(metrics.DefaultRegistry) + http.Handle("/memsize/", http.StripPrefix("/memsize", &Memsize)) + log.Info("Starting pprof server", "addr", fmt.Sprintf("http://%s/debug/pprof", address)) + go func() { + if err := http.ListenAndServe(address, nil); err != nil { + log.Error("Failure in running pprof server", "err", err) + } + }() +} + // Exit stops all running profiles, flushing their output to the // respective file. func Exit() { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 6525aa212c87..e2bfbaf307bb 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" @@ -1388,7 +1389,7 @@ func (api *PublicDebugAPI) PrintBlock(ctx context.Context, number uint64) (strin if block == nil { return "", fmt.Errorf("block #%d not found", number) } - return block.String(), nil + return spew.Sdump(block), nil } // SeedHash retrieves the seed hash of a block. diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js index 9bb899384bba..abd4b4fe5818 100644 --- a/internal/jsre/deps/web3.js +++ b/internal/jsre/deps/web3.js @@ -2193,7 +2193,7 @@ var toWei = function(number, unit) { }; /** - * Takes an input and transforms it into an bignumber + * Takes an input and transforms it into a bignumber * * @method toBigNumber * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber diff --git a/internal/jsre/jsre.go b/internal/jsre/jsre.go index f05865eca60d..4c7664f1ccbd 100644 --- a/internal/jsre/jsre.go +++ b/internal/jsre/jsre.go @@ -102,8 +102,8 @@ func randomSource() *rand.Rand { // call the functions of the otto vm directly to circumvent the queue. These // functions should be used if and only if running a routine that was already // called from JS through an RPC call. -func (self *JSRE) runEventLoop() { - defer close(self.closed) +func (re *JSRE) runEventLoop() { + defer close(re.closed) vm := otto.New() r := randomSource() @@ -202,14 +202,14 @@ loop: break loop } } - case req := <-self.evalQueue: + case req := <-re.evalQueue: // run the code, send the result back req.fn(vm) close(req.done) if waitForCallbacks && (len(registry) == 0) { break loop } - case waitForCallbacks = <-self.stopEventLoop: + case waitForCallbacks = <-re.stopEventLoop: if !waitForCallbacks || (len(registry) == 0) { break loop } @@ -223,31 +223,31 @@ loop: } // Do executes the given function on the JS event loop. -func (self *JSRE) Do(fn func(*otto.Otto)) { +func (re *JSRE) Do(fn func(*otto.Otto)) { done := make(chan bool) req := &evalReq{fn, done} - self.evalQueue <- req + re.evalQueue <- req <-done } // stops the event loop before exit, optionally waits for all timers to expire -func (self *JSRE) Stop(waitForCallbacks bool) { +func (re *JSRE) Stop(waitForCallbacks bool) { select { - case <-self.closed: - case self.stopEventLoop <- waitForCallbacks: - <-self.closed + case <-re.closed: + case re.stopEventLoop <- waitForCallbacks: + <-re.closed } } // Exec(file) loads and runs the contents of a file // if a relative path is given, the jsre's assetPath is used -func (self *JSRE) Exec(file string) error { - code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file)) +func (re *JSRE) Exec(file string) error { + code, err := ioutil.ReadFile(common.AbsolutePath(re.assetPath, file)) if err != nil { return err } var script *otto.Script - self.Do(func(vm *otto.Otto) { + re.Do(func(vm *otto.Otto) { script, err = vm.Compile(file, code) if err != nil { return @@ -259,36 +259,36 @@ func (self *JSRE) Exec(file string) error { // Bind assigns value v to a variable in the JS environment // This method is deprecated, use Set. -func (self *JSRE) Bind(name string, v interface{}) error { - return self.Set(name, v) +func (re *JSRE) Bind(name string, v interface{}) error { + return re.Set(name, v) } // Run runs a piece of JS code. -func (self *JSRE) Run(code string) (v otto.Value, err error) { - self.Do(func(vm *otto.Otto) { v, err = vm.Run(code) }) +func (re *JSRE) Run(code string) (v otto.Value, err error) { + re.Do(func(vm *otto.Otto) { v, err = vm.Run(code) }) return v, err } // Get returns the value of a variable in the JS environment. -func (self *JSRE) Get(ns string) (v otto.Value, err error) { - self.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) }) +func (re *JSRE) Get(ns string) (v otto.Value, err error) { + re.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) }) return v, err } // Set assigns value v to a variable in the JS environment. -func (self *JSRE) Set(ns string, v interface{}) (err error) { - self.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) }) +func (re *JSRE) Set(ns string, v interface{}) (err error) { + re.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) }) return err } // loadScript executes a JS script from inside the currently executing JS code. -func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value { +func (re *JSRE) loadScript(call otto.FunctionCall) otto.Value { file, err := call.Argument(0).ToString() if err != nil { // TODO: throw exception return otto.FalseValue() } - file = common.AbsolutePath(self.assetPath, file) + file = common.AbsolutePath(re.assetPath, file) source, err := ioutil.ReadFile(file) if err != nil { // TODO: throw exception @@ -305,10 +305,10 @@ func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value { // Evaluate executes code and pretty prints the result to the specified output // stream. -func (self *JSRE) Evaluate(code string, w io.Writer) error { +func (re *JSRE) Evaluate(code string, w io.Writer) error { var fail error - self.Do(func(vm *otto.Otto) { + re.Do(func(vm *otto.Otto) { val, err := vm.Run(code) if err != nil { prettyError(vm, err, w) @@ -321,8 +321,8 @@ func (self *JSRE) Evaluate(code string, w io.Writer) error { } // Compile compiles and then runs a piece of JS code. -func (self *JSRE) Compile(filename string, src interface{}) (err error) { - self.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) }) +func (re *JSRE) Compile(filename string, src interface{}) (err error) { + re.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) }) return err } diff --git a/les/bloombits.go b/les/bloombits.go index de233d751830..2871a9006412 100644 --- a/les/bloombits.go +++ b/les/bloombits.go @@ -72,13 +72,3 @@ func (eth *LightEthereum) startBloomHandlers() { }() } } - -const ( - // bloomConfirms is the number of confirmation blocks before a bloom section is - // considered probably final and its rotated bits are calculated. - bloomConfirms = 256 - - // bloomThrottling is the time to wait between processing two consecutive index - // sections. It's useful during chain upgrades to prevent disk overload. - bloomThrottling = 100 * time.Millisecond -) diff --git a/les/peer.go b/les/peer.go index caf568077805..eb7452e276c6 100644 --- a/les/peer.go +++ b/les/peer.go @@ -545,9 +545,11 @@ func (ps *peerSet) notify(n peerSetNotify) { func (ps *peerSet) Register(p *peer) error { ps.lock.Lock() if ps.closed { + ps.lock.Unlock() return errClosed } if _, ok := ps.peers[p.id]; ok { + ps.lock.Unlock() return errAlreadyRegistered } ps.peers[p.id] = p diff --git a/les/serverpool.go b/les/serverpool.go index a84c29c3ac51..da73b4b3c5b2 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -73,7 +73,6 @@ const ( // and a short term value which is adjusted exponentially with a factor of // pstatRecentAdjust with each dial/connection and also returned exponentially // to the average with the time constant pstatReturnToMeanTC - pstatRecentAdjust = 0.1 pstatReturnToMeanTC = time.Hour // node address selection weight is dropped by a factor of exp(-addrFailDropLn) after // each unsuccessful connection (restored after a successful one) @@ -83,9 +82,6 @@ const ( responseScoreTC = time.Millisecond * 100 delayScoreTC = time.Second * 5 timeoutPow = 10 - // peerSelectMinWeight is added to calculated weights at request peer selection - // to give poorly performing peers a little chance of coming back - peerSelectMinWeight = 0.005 // initStatsWeight is used to initialize previously unknown peers with good // statistics to give a chance to prove themselves initStatsWeight = 1 diff --git a/les/sync.go b/les/sync.go index c0e17f97d96b..c3d37e2f3f87 100644 --- a/les/sync.go +++ b/les/sync.go @@ -25,11 +25,6 @@ import ( "github.com/ethereum/go-ethereum/light" ) -const ( - //forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available - minDesiredPeerCount = 5 // Amount of peers desired to start syncing -) - // syncer is responsible for periodically synchronising with the network, both // downloading hashes and blocks as well as handling the announcement handler. func (pm *ProtocolManager) syncer() { diff --git a/light/postprocess.go b/light/postprocess.go index d67c5e2c3e07..702eb53c8637 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -58,18 +58,18 @@ type trustedCheckpoint struct { var ( mainnetCheckpoint = trustedCheckpoint{ name: "mainnet", - sectionIdx: 161, - sectionHead: common.HexToHash("75b0c4baa7a62cece48abdcb03b6f31601961c9bece67dcd61df87aad4fc0d8d"), - chtRoot: common.HexToHash("bbbfaa67b29716348997ec21a39c03b8d1fb973f6a43740b865595ba26ee812f"), - bloomTrieRoot: common.HexToHash("d6db6e6248354d7453391ce97830072a28ea4216be0bd95a5db9f53b1a64677b"), + sectionIdx: 165, + sectionHead: common.HexToHash("21028acf9cd9ce80257221adc437c3c58ce046c4d43c21c3e9b1d1349059ec73"), + chtRoot: common.HexToHash("26b2458cb7d0080d3a39311c914be92c368777a65ec074e1893b8bdc79e3910a"), + bloomTrieRoot: common.HexToHash("5d06908769179186165a72db7fc3473b25c28ed27efe78a392a9ff2c3fa67f84"), } ropstenCheckpoint = trustedCheckpoint{ name: "ropsten", - sectionIdx: 83, - sectionHead: common.HexToHash("3ca623586bc0da35f1fc8d9b6b55950f3b1f69be9c6501846a2df672adb61236"), - chtRoot: common.HexToHash("8f08ec7783969768c6ef06e5fe3398223cbf4ae2907b676da7b6fe6c7f55b059"), - bloomTrieRoot: common.HexToHash("02d86d3c6a87f8f8a92c2a59bbba2132ff6f9f61b0915a5dc28a9d8279219fd0"), + sectionIdx: 92, + sectionHead: common.HexToHash("21a158f9cc643da13a237cafceb37381072649f7278cf98c5820bfbced7cfcec"), + chtRoot: common.HexToHash("1a8ddb8b086d7a33ca90eea90730225948fa504ae0283b15aff3c15c0e089bf9"), + bloomTrieRoot: common.HexToHash("fd192f92afbcdd0020c81ca0625116b5995509659653b10123bd986fe5129cc1"), } ) diff --git a/miner/worker.go b/miner/worker.go index 15395ae0b93c..0075db118fd2 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -117,6 +117,10 @@ type worker struct { currentMu sync.Mutex current *Work + snapshotMu sync.RWMutex + snapshotBlock *types.Block + snapshotState *state.StateDB + uncleMu sync.Mutex possibleUncles map[common.Hash]*types.Block @@ -171,32 +175,28 @@ func (self *worker) setExtra(extra []byte) { } func (self *worker) pending() (*types.Block, *state.StateDB) { - self.currentMu.Lock() - defer self.currentMu.Unlock() - if atomic.LoadInt32(&self.mining) == 0 { - return types.NewBlock( - self.current.header, - self.current.txs, - nil, - self.current.receipts, - ), self.current.state.Copy() + // return a snapshot to avoid contention on currentMu mutex + self.snapshotMu.RLock() + defer self.snapshotMu.RUnlock() + return self.snapshotBlock, self.snapshotState.Copy() } - return self.current.Block, self.current.state.Copy() -} -func (self *worker) pendingBlock() *types.Block { self.currentMu.Lock() defer self.currentMu.Unlock() + return self.current.Block, self.current.state.Copy() +} +func (self *worker) pendingBlock() *types.Block { if atomic.LoadInt32(&self.mining) == 0 { - return types.NewBlock( - self.current.header, - self.current.txs, - nil, - self.current.receipts, - ) + // return a snapshot to avoid contention on currentMu mutex + self.snapshotMu.RLock() + defer self.snapshotMu.RUnlock() + return self.snapshotBlock } + + self.currentMu.Lock() + defer self.currentMu.Unlock() return self.current.Block } @@ -268,6 +268,7 @@ func (self *worker) update() { txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs) self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase) + self.updateSnapshot() self.currentMu.Unlock() } else { // If we're mining, but nothing is being processed, wake on new transactions @@ -403,7 +404,7 @@ func (self *worker) commitNewWork() { } // this will ensure we're not going off too far in the future if now := time.Now().Unix(); tstamp > now+1 { - wait := time.Duration(tstamp-now) * time.Second + wait := time.Duration(tstamp-now-1) * time.Second log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) time.Sleep(wait) } @@ -489,6 +490,7 @@ func (self *worker) commitNewWork() { self.unconfirmed.Shift(work.Block.NumberU64() - 1) } self.push(work) + self.updateSnapshot() } func (self *worker) commitUncle(work *Work, uncle *types.Header) error { @@ -506,6 +508,19 @@ func (self *worker) commitUncle(work *Work, uncle *types.Header) error { return nil } +func (self *worker) updateSnapshot() { + self.snapshotMu.Lock() + defer self.snapshotMu.Unlock() + + self.snapshotBlock = types.NewBlock( + self.current.header, + self.current.txs, + nil, + self.current.receipts, + ) + self.snapshotState = self.current.state.Copy() +} + func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) { gp := new(core.GasPool).AddGas(env.header.GasLimit) diff --git a/mobile/geth.go b/mobile/geth.go index 488a4150fcab..645b360eb3a9 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethstats" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -72,6 +73,9 @@ type NodeConfig struct { // WhisperEnabled specifies whether the node should run the Whisper protocol. WhisperEnabled bool + + // Listening address of pprof server. + PprofAddress string } // defaultNodeConfig contains the default node configuration values to use if all @@ -107,6 +111,11 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { if config.BootstrapNodes == nil || config.BootstrapNodes.Size() == 0 { config.BootstrapNodes = defaultNodeConfig.BootstrapNodes } + + if config.PprofAddress != "" { + debug.StartPProf(config.PprofAddress) + } + // Create the empty networking stack nodeConf := &node.Config{ Name: clientIdentifier, @@ -127,6 +136,8 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { return nil, err } + debug.Memsize.Add("node", rawStack) + var genesis *core.Genesis if config.EthereumGenesis != "" { // Parse the user supplied genesis spec if not mainnet diff --git a/mobile/types.go b/mobile/types.go index 4790afceff6e..196c7839f536 100644 --- a/mobile/types.go +++ b/mobile/types.go @@ -80,7 +80,7 @@ func (h *Header) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(h.header) } -// NewHeaderFromJSON parses a header from an JSON data dump. +// NewHeaderFromJSON parses a header from a JSON data dump. func NewHeaderFromJSON(data string) (*Header, error) { h := &Header{ header: new(types.Header), @@ -91,18 +91,12 @@ func NewHeaderFromJSON(data string) (*Header, error) { return h, nil } -// EncodeJSON encodes a header into an JSON data dump. +// EncodeJSON encodes a header into a JSON data dump. func (h *Header) EncodeJSON() (string, error) { data, err := json.Marshal(h.header) return string(data), err } -// String implements the fmt.Stringer interface to print some semi-meaningful -// data dump of the header for debugging purposes. -func (h *Header) String() string { - return h.header.String() -} - func (h *Header) GetParentHash() *Hash { return &Hash{h.header.ParentHash} } func (h *Header) GetUncleHash() *Hash { return &Hash{h.header.UncleHash} } func (h *Header) GetCoinbase() *Address { return &Address{h.header.Coinbase} } @@ -157,7 +151,7 @@ func (b *Block) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(b.block) } -// NewBlockFromJSON parses a block from an JSON data dump. +// NewBlockFromJSON parses a block from a JSON data dump. func NewBlockFromJSON(data string) (*Block, error) { b := &Block{ block: new(types.Block), @@ -168,18 +162,12 @@ func NewBlockFromJSON(data string) (*Block, error) { return b, nil } -// EncodeJSON encodes a block into an JSON data dump. +// EncodeJSON encodes a block into a JSON data dump. func (b *Block) EncodeJSON() (string, error) { data, err := json.Marshal(b.block) return string(data), err } -// String implements the fmt.Stringer interface to print some semi-meaningful -// data dump of the block for debugging purposes. -func (b *Block) String() string { - return b.block.String() -} - func (b *Block) GetParentHash() *Hash { return &Hash{b.block.ParentHash()} } func (b *Block) GetUncleHash() *Hash { return &Hash{b.block.UncleHash()} } func (b *Block) GetCoinbase() *Address { return &Address{b.block.Coinbase()} } @@ -232,7 +220,7 @@ func (tx *Transaction) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(tx.tx) } -// NewTransactionFromJSON parses a transaction from an JSON data dump. +// NewTransactionFromJSON parses a transaction from a JSON data dump. func NewTransactionFromJSON(data string) (*Transaction, error) { tx := &Transaction{ tx: new(types.Transaction), @@ -243,18 +231,12 @@ func NewTransactionFromJSON(data string) (*Transaction, error) { return tx, nil } -// EncodeJSON encodes a transaction into an JSON data dump. +// EncodeJSON encodes a transaction into a JSON data dump. func (tx *Transaction) EncodeJSON() (string, error) { data, err := json.Marshal(tx.tx) return string(data), err } -// String implements the fmt.Stringer interface to print some semi-meaningful -// data dump of the transaction for debugging purposes. -func (tx *Transaction) String() string { - return tx.tx.String() -} - func (tx *Transaction) GetData() []byte { return tx.tx.Data() } func (tx *Transaction) GetGas() int64 { return int64(tx.tx.Gas()) } func (tx *Transaction) GetGasPrice() *BigInt { return &BigInt{tx.tx.GasPrice()} } @@ -330,7 +312,7 @@ func (r *Receipt) EncodeRLP() ([]byte, error) { return rlp.EncodeToBytes(r.receipt) } -// NewReceiptFromJSON parses a transaction receipt from an JSON data dump. +// NewReceiptFromJSON parses a transaction receipt from a JSON data dump. func NewReceiptFromJSON(data string) (*Receipt, error) { r := &Receipt{ receipt: new(types.Receipt), @@ -341,18 +323,12 @@ func NewReceiptFromJSON(data string) (*Receipt, error) { return r, nil } -// EncodeJSON encodes a transaction receipt into an JSON data dump. +// EncodeJSON encodes a transaction receipt into a JSON data dump. func (r *Receipt) EncodeJSON() (string, error) { data, err := rlp.EncodeToBytes(r.receipt) return string(data), err } -// String implements the fmt.Stringer interface to print some semi-meaningful -// data dump of the transaction receipt for debugging purposes. -func (r *Receipt) String() string { - return r.receipt.String() -} - func (r *Receipt) GetPostState() []byte { return r.receipt.PostState } func (r *Receipt) GetCumulativeGasUsed() int64 { return int64(r.receipt.CumulativeGasUsed) } func (r *Receipt) GetBloom() *Bloom { return &Bloom{r.receipt.Bloom} } diff --git a/node/config.go b/node/config.go index dda24583ee4e..486eddf9252e 100644 --- a/node/config.go +++ b/node/config.go @@ -209,7 +209,7 @@ func DefaultHTTPEndpoint() string { return config.HTTPEndpoint() } -// WSEndpoint resolves an websocket endpoint based on the configured host interface +// WSEndpoint resolves a websocket endpoint based on the configured host interface // and port parameters. func (c *Config) WSEndpoint() string { if c.WSHost == "" { diff --git a/node/node.go b/node/node.go index b02aecfad15f..83b6c4c07e6c 100644 --- a/node/node.go +++ b/node/node.go @@ -303,50 +303,16 @@ func (n *Node) stopInProc() { // startIPC initializes and starts the IPC RPC endpoint. func (n *Node) startIPC(apis []rpc.API) error { - // Short circuit if the IPC endpoint isn't being exposed if n.ipcEndpoint == "" { - return nil + return nil // IPC disabled. } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("IPC registered", "service", api.Service, "namespace", api.Namespace) - } - // All APIs registered, start the IPC listener - var ( - listener net.Listener - err error - ) - if listener, err = rpc.CreateIPCListener(n.ipcEndpoint); err != nil { + listener, handler, err := rpc.StartIPCEndpoint(n.ipcEndpoint, apis) + if err != nil { return err } - go func() { - n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) - - for { - conn, err := listener.Accept() - if err != nil { - // Terminate if the listener was closed - n.lock.RLock() - closed := n.ipcListener == nil - n.lock.RUnlock() - if closed { - return - } - // Not closed, just some error; report and continue - n.log.Error("IPC accept failed", "err", err) - continue - } - go handler.ServeCodec(rpc.NewJSONCodec(conn), rpc.OptionMethodInvocation|rpc.OptionSubscriptions) - } - }() - // All listeners booted successfully n.ipcListener = listener n.ipcHandler = handler - + n.log.Info("IPC endpoint opened", "url", n.ipcEndpoint) return nil } @@ -370,30 +336,10 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors if endpoint == "" { return nil } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("HTTP registered", "service", api.Service, "namespace", api.Namespace) - } - } - // All APIs registered, start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { + listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts) + if err != nil { return err } - go rpc.NewHTTPServer(cors, vhosts, handler).Serve(listener) n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) // All listeners booted successfully n.httpEndpoint = endpoint @@ -423,32 +369,11 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig if endpoint == "" { return nil } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - handler := rpc.NewServer() - for _, api := range apis { - if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return err - } - n.log.Debug("WebSocket registered", "service", api.Service, "namespace", api.Namespace) - } - } - // All APIs registered, start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { + listener, handler, err := rpc.StartWSEndpoint(endpoint, apis, modules, wsOrigins, exposeAll) + if err != nil { return err } - go rpc.NewWSServer(wsOrigins, handler).Serve(listener) n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%s", listener.Addr())) - // All listeners booted successfully n.wsEndpoint = endpoint n.wsListener = listener diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 524c6e498828..f6bcd9708571 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -49,7 +49,6 @@ var ( // Timeouts const ( respTimeout = 500 * time.Millisecond - sendTimeout = 500 * time.Millisecond expiration = 20 * time.Second ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index 52c677b623b3..9b0bd0c80ac6 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -36,7 +36,6 @@ import ( var ( errInvalidEvent = errors.New("invalid in current state") errNoQuery = errors.New("no pending query") - errWrongAddress = errors.New("unknown sender address") ) const ( @@ -828,11 +827,10 @@ type nodeEvent uint //go:generate stringer -type=nodeEvent const ( - invalidEvent nodeEvent = iota // zero is reserved // Packet type events. // These correspond to packet types in the UDP protocol. - pingPacket + pingPacket = iota + 1 pongPacket findnodePacket neighborsPacket diff --git a/p2p/discv5/nodeevent_string.go b/p2p/discv5/nodeevent_string.go index eb696fb8beef..38c1993bacbf 100644 --- a/p2p/discv5/nodeevent_string.go +++ b/p2p/discv5/nodeevent_string.go @@ -4,24 +4,14 @@ package discv5 import "strconv" -const ( - _nodeEvent_name_0 = "invalidEventpingPacketpongPacketfindnodePacketneighborsPacketfindnodeHashPackettopicRegisterPackettopicQueryPackettopicNodesPacket" - _nodeEvent_name_1 = "pongTimeoutpingTimeoutneighboursTimeout" -) +const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout" -var ( - _nodeEvent_index_0 = [...]uint8{0, 12, 22, 32, 46, 61, 79, 98, 114, 130} - _nodeEvent_index_1 = [...]uint8{0, 11, 22, 39} -) +var _nodeEvent_index = [...]uint8{0, 11, 22, 39} func (i nodeEvent) String() string { - switch { - case 0 <= i && i <= 8: - return _nodeEvent_name_0[_nodeEvent_index_0[i]:_nodeEvent_index_0[i+1]] - case 265 <= i && i <= 267: - i -= 265 - return _nodeEvent_name_1[_nodeEvent_index_1[i]:_nodeEvent_index_1[i+1]] - default: - return "nodeEvent(" + strconv.FormatInt(int64(i), 10) + ")" + i -= 264 + if i >= nodeEvent(len(_nodeEvent_index)-1) { + return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")" } + return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]] } diff --git a/p2p/discv5/table.go b/p2p/discv5/table.go index 2cf05009cb0f..c8d234b936d4 100644 --- a/p2p/discv5/table.go +++ b/p2p/discv5/table.go @@ -38,7 +38,6 @@ const ( hashBits = len(common.Hash{}) * 8 nBuckets = hashBits + 1 // Number of buckets - maxBondingPingPongs = 16 maxFindnodeFailures = 5 ) diff --git a/p2p/discv5/udp.go b/p2p/discv5/udp.go index 6ce72d2c152b..09e5f8b37409 100644 --- a/p2p/discv5/udp.go +++ b/p2p/discv5/udp.go @@ -36,25 +36,17 @@ const Version = 4 // Errors var ( - errPacketTooSmall = errors.New("too small") - errBadPrefix = errors.New("bad prefix") - errExpired = errors.New("expired") - errUnsolicitedReply = errors.New("unsolicited reply") - errUnknownNode = errors.New("unknown node") - errTimeout = errors.New("RPC timeout") - errClockWarp = errors.New("reply deadline too far in the future") - errClosed = errors.New("socket closed") + errPacketTooSmall = errors.New("too small") + errBadPrefix = errors.New("bad prefix") + errTimeout = errors.New("RPC timeout") ) // Timeouts const ( respTimeout = 500 * time.Millisecond - queryDelay = 1000 * time.Millisecond expiration = 20 * time.Second - ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP - ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning - driftThreshold = 10 * time.Second // Allowed clock drift before warning user + driftThreshold = 10 * time.Second // Allowed clock drift before warning user ) // RPC request structures diff --git a/p2p/discv5/udp_test.go b/p2p/discv5/udp_test.go index 7d31815947cf..62184aa9d3bf 100644 --- a/p2p/discv5/udp_test.go +++ b/p2p/discv5/udp_test.go @@ -24,7 +24,6 @@ import ( "reflect" "sync" "testing" - "time" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" @@ -38,11 +37,7 @@ func init() { // shared test variables var ( - futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) - testTarget = NodeID{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1} - testRemote = rpcEndpoint{IP: net.ParseIP("1.1.1.1").To4(), UDP: 1, TCP: 2} - testLocalAnnounced = rpcEndpoint{IP: net.ParseIP("2.2.2.2").To4(), UDP: 3, TCP: 4} - testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} + testLocal = rpcEndpoint{IP: net.ParseIP("3.3.3.3").To4(), UDP: 5, TCP: 6} ) // type udpTest struct { diff --git a/p2p/enr/enr.go b/p2p/enr/enr.go index 2c3afb43e996..c018895cc0ce 100644 --- a/p2p/enr/enr.go +++ b/p2p/enr/enr.go @@ -46,7 +46,6 @@ const ID_SECP256k1_KECCAK = ID("secp256k1-keccak") // the default identity schem var ( errNoID = errors.New("unknown or unspecified identity scheme") - errInvalidSigsize = errors.New("invalid signature size") errInvalidSig = errors.New("invalid signature") errNotSorted = errors.New("record key/value pairs are not sorted by key") errDuplicateKey = errors.New("record contains duplicate key") diff --git a/p2p/peer.go b/p2p/peer.go index 477d8c219048..73e33418ed0a 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -47,8 +47,6 @@ const ( discMsg = 0x01 pingMsg = 0x02 pongMsg = 0x03 - getPeersMsg = 0x04 - peersMsg = 0x05 ) // protoHandshake is the RLP structure of the protocol handshake. diff --git a/params/protocol_params.go b/params/protocol_params.go index 5a0b14d61a30..2861921cec25 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -81,7 +81,7 @@ const ( var ( DifficultyBoundDivisor = big.NewInt(2048) // The bound divisor of the difficulty, used in the update calculations. - GenesisDifficulty = big.NewInt(131072) // Difficulty of the Genesis block. - MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. + GenesisDifficulty = big.NewInt(2048) // 8192 Difficulty of the Genesis block. + MinimumDifficulty = big.NewInt(2048) // 8192 The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. ) diff --git a/params/version.go b/params/version.go index e37ea2d39f22..dd8436824983 100644 --- a/params/version.go +++ b/params/version.go @@ -23,7 +23,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 3 // Patch version component of the current release + VersionPatch = 7 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) diff --git a/rpc/client.go b/rpc/client.go index 8aa84ec98279..77b4d5ee0186 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -25,6 +25,7 @@ import ( "fmt" "net" "net/url" + "os" "reflect" "strconv" "strings" @@ -171,6 +172,8 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { return DialHTTP(rawurl) case "ws", "wss": return DialWebsocket(ctx, rawurl, "") + case "stdio": + return DialStdIO(ctx) case "": return DialIPC(ctx, rawurl) default: @@ -178,13 +181,51 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { } } +type StdIOConn struct{} + +func (io StdIOConn) Read(b []byte) (n int, err error) { + return os.Stdin.Read(b) +} + +func (io StdIOConn) Write(b []byte) (n int, err error) { + return os.Stdout.Write(b) +} + +func (io StdIOConn) Close() error { + return nil +} + +func (io StdIOConn) LocalAddr() net.Addr { + return &net.UnixAddr{Name: "stdio", Net: "stdio"} +} + +func (io StdIOConn) RemoteAddr() net.Addr { + return &net.UnixAddr{Name: "stdio", Net: "stdio"} +} + +func (io StdIOConn) SetDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (io StdIOConn) SetReadDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} + +func (io StdIOConn) SetWriteDeadline(t time.Time) error { + return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +} +func DialStdIO(ctx context.Context) (*Client, error) { + return newClient(ctx, func(_ context.Context) (net.Conn, error) { + return StdIOConn{}, nil + }) +} + func newClient(initctx context.Context, connectFunc func(context.Context) (net.Conn, error)) (*Client, error) { conn, err := connectFunc(initctx) if err != nil { return nil, err } _, isHTTP := conn.(*httpConn) - c := &Client{ writeConn: conn, isHTTP: isHTTP, @@ -524,13 +565,13 @@ func (c *Client) dispatch(conn net.Conn) { } case err := <-c.readErr: - log.Debug(fmt.Sprintf("<-readErr: %v", err)) + log.Debug("<-readErr", "err", err) c.closeRequestOps(err) conn.Close() reading = false case newconn := <-c.reconnected: - log.Debug(fmt.Sprintf("<-reconnected: (reading=%t) %v", reading, conn.RemoteAddr())) + log.Debug("<-reconnected", "reading", reading, "remote", conn.RemoteAddr()) if reading { // Wait for the previous read loop to exit. This is a rare case. conn.Close() @@ -587,7 +628,7 @@ func (c *Client) closeRequestOps(err error) { func (c *Client) handleNotification(msg *jsonrpcMessage) { if !strings.HasSuffix(msg.Method, notificationMethodSuffix) { - log.Debug(fmt.Sprint("dropping non-subscription message: ", msg)) + log.Debug("dropping non-subscription message", "msg", msg) return } var subResult struct { @@ -595,7 +636,7 @@ func (c *Client) handleNotification(msg *jsonrpcMessage) { Result json.RawMessage `json:"result"` } if err := json.Unmarshal(msg.Params, &subResult); err != nil { - log.Debug(fmt.Sprint("dropping invalid subscription message: ", msg)) + log.Debug("dropping invalid subscription message", "msg", msg) return } if c.subs[subResult.ID] != nil { @@ -606,7 +647,7 @@ func (c *Client) handleNotification(msg *jsonrpcMessage) { func (c *Client) handleResponse(msg *jsonrpcMessage) { op := c.respWait[string(msg.ID)] if op == nil { - log.Debug(fmt.Sprintf("unsolicited response %v", msg)) + log.Debug("unsolicited response", "msg", msg) return } delete(c.respWait, string(msg.ID)) diff --git a/rpc/doc.go b/rpc/doc.go index 14b3780ade00..78aa92f899ca 100644 --- a/rpc/doc.go +++ b/rpc/doc.go @@ -92,7 +92,7 @@ An example method: Subscriptions are deleted when: - the user sends an unsubscribe request - the connection which was used to create the subscription is closed. This can be initiated - by the client and server. The server will close the connection on an write error or when + by the client and server. The server will close the connection on a write error or when the queue of buffered notifications gets too big. */ package rpc diff --git a/rpc/endpoints.go b/rpc/endpoints.go new file mode 100644 index 000000000000..692c62d3a49d --- /dev/null +++ b/rpc/endpoints.go @@ -0,0 +1,102 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +import ( + "net" + + "github.com/ethereum/go-ethereum/log" +) + +// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules +func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string) (net.Listener, *Server, error) { + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + handler := NewServer() + for _, api := range apis { + if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return nil, nil, err + } + log.Debug("HTTP registered", "namespace", api.Namespace) + } + } + // All APIs registered, start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, nil, err + } + go NewHTTPServer(cors, vhosts, handler).Serve(listener) + return listener, handler, err +} + +// StartWSEndpoint starts a websocket endpoint +func StartWSEndpoint(endpoint string, apis []API, modules []string, wsOrigins []string, exposeAll bool) (net.Listener, *Server, error) { + + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + handler := NewServer() + for _, api := range apis { + if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return nil, nil, err + } + log.Debug("WebSocket registered", "service", api.Service, "namespace", api.Namespace) + } + } + // All APIs registered, start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, nil, err + } + go NewWSServer(wsOrigins, handler).Serve(listener) + return listener, handler, err + +} + +// StartIPCEndpoint starts an IPC endpoint. +func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) { + // Register all the APIs exposed by the services. + handler := NewServer() + for _, api := range apis { + if err := handler.RegisterName(api.Namespace, api.Service); err != nil { + return nil, nil, err + } + log.Debug("IPC registered", "namespace", api.Namespace) + } + // All APIs registered, start the IPC listener. + listener, err := ipcListen(ipcEndpoint) + if err != nil { + return nil, nil, err + } + go handler.ServeListener(listener) + return listener, handler, nil +} diff --git a/rpc/http.go b/rpc/http.go index e8f51150f4a7..0c2e060fb44b 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -90,10 +90,19 @@ func DialHTTP(endpoint string) (*Client, error) { func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { hc := c.writeConn.(*httpConn) respBody, err := hc.doRequest(ctx, msg) + if respBody != nil { + defer respBody.Close() + } + if err != nil { + if respBody != nil { + buf := new(bytes.Buffer) + if _, err2 := buf.ReadFrom(respBody); err2 == nil { + return fmt.Errorf("%v %v", err, buf.String()) + } + } return err } - defer respBody.Close() var respmsg jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { return err @@ -132,6 +141,9 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos if err != nil { return nil, err } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return resp.Body, errors.New(resp.Status) + } return resp.Body, nil } @@ -169,12 +181,17 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // All checks passed, create a codec that reads direct from the request body // untilEOF and writes the response to w and order the server to process a // single request. + ctx := context.Background() + ctx = context.WithValue(ctx, "remote", r.RemoteAddr) + ctx = context.WithValue(ctx, "scheme", r.Proto) + ctx = context.WithValue(ctx, "local", r.Host) + body := io.LimitReader(r.Body, maxRequestContentLength) codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) defer codec.Close() w.Header().Set("content-type", contentType) - srv.ServeSingleRequest(codec, OptionMethodInvocation) + srv.ServeSingleRequest(codec, OptionMethodInvocation, ctx) } // validateRequest returns a non-zero response code and error message if the diff --git a/rpc/ipc.go b/rpc/ipc.go index 8de18a56feda..b05e503d7427 100644 --- a/rpc/ipc.go +++ b/rpc/ipc.go @@ -18,26 +18,23 @@ package rpc import ( "context" - "fmt" "net" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/netutil" ) -// CreateIPCListener creates an listener, on Unix platforms this is a unix socket, on -// Windows this is a named pipe -func CreateIPCListener(endpoint string) (net.Listener, error) { - return ipcListen(endpoint) -} - // ServeListener accepts connections on l, serving JSON-RPC on them. func (srv *Server) ServeListener(l net.Listener) error { for { conn, err := l.Accept() - if err != nil { + if netutil.IsTemporaryError(err) { + log.Warn("RPC accept error", "err", err) + continue + } else if err != nil { return err } - log.Trace(fmt.Sprint("accepted conn", conn.RemoteAddr())) + log.Trace("Accepted connection", "addr", conn.RemoteAddr()) go srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions) } } diff --git a/rpc/server.go b/rpc/server.go index 11373b504cc4..0f29035edbca 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -125,7 +125,7 @@ func (s *Server) RegisterName(name string, rcvr interface{}) error { // If singleShot is true it will process a single request, otherwise it will handle // requests until the codec returns an error when reading a request (in most cases // an EOF). It executes requests in parallel when singleShot is false. -func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecOption) error { +func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecOption, ctx context.Context) error { var pend sync.WaitGroup defer func() { @@ -140,7 +140,8 @@ func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecO s.codecsMu.Unlock() }() - ctx, cancel := context.WithCancel(context.Background()) + // ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() // if the codec supports notification include a notifier that callbacks can use @@ -215,14 +216,14 @@ func (s *Server) serveRequest(codec ServerCodec, singleShot bool, options CodecO // stopped. In either case the codec is closed. func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { defer codec.Close() - s.serveRequest(codec, false, options) + s.serveRequest(codec, false, options, context.Background()) } // ServeSingleRequest reads and processes a single RPC request from the given codec. It will not // close the codec unless a non-recoverable error has occurred. Note, this method will return after // a single request has been processed! -func (s *Server) ServeSingleRequest(codec ServerCodec, options CodecOption) { - s.serveRequest(codec, true, options) +func (s *Server) ServeSingleRequest(codec ServerCodec, options CodecOption, ctx context.Context) { + s.serveRequest(codec, true, options, ctx) } // Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish, diff --git a/signer/core/abihelper.go b/signer/core/abihelper.go new file mode 100644 index 000000000000..2674c7346a5b --- /dev/null +++ b/signer/core/abihelper.go @@ -0,0 +1,256 @@ +// 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 . + +package core + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "bytes" + "os" + "regexp" +) + +type decodedArgument struct { + soltype abi.Argument + value interface{} +} +type decodedCallData struct { + signature string + name string + inputs []decodedArgument +} + +// String implements stringer interface, tries to use the underlying value-type +func (arg decodedArgument) String() string { + var value string + switch arg.value.(type) { + case fmt.Stringer: + value = arg.value.(fmt.Stringer).String() + default: + value = fmt.Sprintf("%v", arg.value) + } + return fmt.Sprintf("%v: %v", arg.soltype.Type.String(), value) +} + +// String implements stringer interface for decodedCallData +func (cd decodedCallData) String() string { + args := make([]string, len(cd.inputs)) + for i, arg := range cd.inputs { + args[i] = arg.String() + } + return fmt.Sprintf("%s(%s)", cd.name, strings.Join(args, ",")) +} + +// parseCallData matches the provided call data against the abi definition, +// and returns a struct containing the actual go-typed values +func parseCallData(calldata []byte, abidata string) (*decodedCallData, error) { + + if len(calldata) < 4 { + return nil, fmt.Errorf("Invalid ABI-data, incomplete method signature of (%d bytes)", len(calldata)) + } + + sigdata, argdata := calldata[:4], calldata[4:] + if len(argdata)%32 != 0 { + return nil, fmt.Errorf("Not ABI-encoded data; length should be a multiple of 32 (was %d)", len(argdata)) + } + + abispec, err := abi.JSON(strings.NewReader(abidata)) + if err != nil { + return nil, fmt.Errorf("Failed parsing JSON ABI: %v, abidata: %v", err, abidata) + } + + method, err := abispec.MethodById(sigdata) + if err != nil { + return nil, err + } + + v, err := method.Inputs.UnpackValues(argdata) + if err != nil { + return nil, err + } + + decoded := decodedCallData{signature: method.Sig(), name: method.Name} + + for n, argument := range method.Inputs { + if err != nil { + return nil, fmt.Errorf("Failed to decode argument %d (signature %v): %v", n, method.Sig(), err) + } else { + decodedArg := decodedArgument{ + soltype: argument, + value: v[n], + } + decoded.inputs = append(decoded.inputs, decodedArg) + } + } + + // We're finished decoding the data. At this point, we encode the decoded data to see if it matches with the + // original data. If we didn't do that, it would e.g. be possible to stuff extra data into the arguments, which + // is not detected by merely decoding the data. + + var ( + encoded []byte + ) + encoded, err = method.Inputs.PackValues(v) + + if err != nil { + return nil, err + } + + if !bytes.Equal(encoded, argdata) { + was := common.Bytes2Hex(encoded) + exp := common.Bytes2Hex(argdata) + return nil, fmt.Errorf("WARNING: Supplied data is stuffed with extra data. \nWant %s\nHave %s\nfor method %v", exp, was, method.Sig()) + } + return &decoded, nil +} + +// MethodSelectorToAbi converts a method selector into an ABI struct. The returned data is a valid json string +// which can be consumed by the standard abi package. +func MethodSelectorToAbi(selector string) ([]byte, error) { + + re := regexp.MustCompile(`^([^\)]+)\(([a-z0-9,\[\]]*)\)`) + + type fakeArg struct { + Type string `json:"type"` + } + type fakeABI struct { + Name string `json:"name"` + Type string `json:"type"` + Inputs []fakeArg `json:"inputs"` + } + groups := re.FindStringSubmatch(selector) + if len(groups) != 3 { + return nil, fmt.Errorf("Did not match: %v (%v matches)", selector, len(groups)) + } + name := groups[1] + args := groups[2] + arguments := make([]fakeArg, 0) + if len(args) > 0 { + for _, arg := range strings.Split(args, ",") { + arguments = append(arguments, fakeArg{arg}) + } + } + abicheat := fakeABI{ + name, "function", arguments, + } + return json.Marshal([]fakeABI{abicheat}) + +} + +type AbiDb struct { + db map[string]string + customdb map[string]string + customdbPath string +} + +// NewEmptyAbiDB exists for test purposes +func NewEmptyAbiDB() (*AbiDb, error) { + return &AbiDb{make(map[string]string), make(map[string]string), ""}, nil +} + +// NewAbiDBFromFile loads signature database from file, and +// errors if the file is not valid json. Does no other validation of contents +func NewAbiDBFromFile(path string) (*AbiDb, error) { + raw, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + db, err := NewEmptyAbiDB() + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.db) + return db, nil +} + +// NewAbiDBFromFiles loads both the standard signature database and a custom database. The latter will be used +// to write new values into if they are submitted via the API +func NewAbiDBFromFiles(standard, custom string) (*AbiDb, error) { + + db := &AbiDb{make(map[string]string), make(map[string]string), custom} + db.customdbPath = custom + + raw, err := ioutil.ReadFile(standard) + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.db) + // Custom file may not exist. Will be created during save, if needed + if _, err := os.Stat(custom); err == nil { + raw, err = ioutil.ReadFile(custom) + if err != nil { + return nil, err + } + json.Unmarshal(raw, &db.customdb) + } + + return db, nil +} + +// LookupMethodSelector checks the given 4byte-sequence against the known ABI methods. +// OBS: This method does not validate the match, it's assumed the caller will do so +func (db *AbiDb) LookupMethodSelector(id []byte) (string, error) { + if len(id) < 4 { + return "", fmt.Errorf("Expected 4-byte id, got %d", len(id)) + } + sig := common.ToHex(id[:4]) + if key, exists := db.db[sig]; exists { + return key, nil + } + if key, exists := db.customdb[sig]; exists { + return key, nil + } + return "", fmt.Errorf("Signature %v not found", sig) +} +func (db *AbiDb) Size() int { + return len(db.db) +} + +// saveCustomAbi saves a signature ephemerally. If custom file is used, also saves to disk +func (db *AbiDb) saveCustomAbi(selector, signature string) error { + db.customdb[signature] = selector + if db.customdbPath == "" { + return nil //Not an error per se, just not used + } + d, err := json.Marshal(db.customdb) + if err != nil { + return err + } + err = ioutil.WriteFile(db.customdbPath, d, 0600) + return err +} + +// Adds a signature to the database, if custom database saving is enabled. +// OBS: This method does _not_ validate the correctness of the data, +// it is assumed that the caller has already done so +func (db *AbiDb) AddSignature(selector string, data []byte) error { + if len(data) < 4 { + return nil + } + _, err := db.LookupMethodSelector(data[:4]) + if err == nil { + return nil + } + sig := common.ToHex(data[:4]) + return db.saveCustomAbi(selector, sig) +} diff --git a/signer/core/abihelper_test.go b/signer/core/abihelper_test.go new file mode 100644 index 000000000000..8bb57766914a --- /dev/null +++ b/signer/core/abihelper_test.go @@ -0,0 +1,247 @@ +// 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 . + +package core + +import ( + "fmt" + "strings" + "testing" + + "io/ioutil" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func verify(t *testing.T, jsondata, calldata string, exp []interface{}) { + + abispec, err := abi.JSON(strings.NewReader(jsondata)) + if err != nil { + t.Fatal(err) + } + cd := common.Hex2Bytes(calldata) + sigdata, argdata := cd[:4], cd[4:] + method, err := abispec.MethodById(sigdata) + + if err != nil { + t.Fatal(err) + } + + data, err := method.Inputs.UnpackValues(argdata) + + if len(data) != len(exp) { + t.Fatalf("Mismatched length, expected %d, got %d", len(exp), len(data)) + } + for i, elem := range data { + if !reflect.DeepEqual(elem, exp[i]) { + t.Fatalf("Unpack error, arg %d, got %v, want %v", i, elem, exp[i]) + } + } +} +func TestNewUnpacker(t *testing.T) { + type unpackTest struct { + jsondata string + calldata string + exp []interface{} + } + testcases := []unpackTest{ + { // https://solidity.readthedocs.io/en/develop/abi-spec.html#use-of-dynamic-types + `[{"type":"function","name":"f", "inputs":[{"type":"uint256"},{"type":"uint32[]"},{"type":"bytes10"},{"type":"bytes"}]}]`, + // 0x123, [0x456, 0x789], "1234567890", "Hello, world!" + "8be65246" + "00000000000000000000000000000000000000000000000000000000000001230000000000000000000000000000000000000000000000000000000000000080313233343536373839300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004560000000000000000000000000000000000000000000000000000000000000789000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000", + []interface{}{ + big.NewInt(0x123), + []uint32{0x456, 0x789}, + [10]byte{49, 50, 51, 52, 53, 54, 55, 56, 57, 48}, + common.Hex2Bytes("48656c6c6f2c20776f726c6421"), + }, + }, { // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#examples + `[{"type":"function","name":"sam","inputs":[{"type":"bytes"},{"type":"bool"},{"type":"uint256[]"}]}]`, + // "dave", true and [1,2,3] + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + []interface{}{ + []byte{0x64, 0x61, 0x76, 0x65}, + true, + []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}, + }, + }, { + `[{"type":"function","name":"send","inputs":[{"type":"uint256"}]}]`, + "a52c101e0000000000000000000000000000000000000000000000000000000000000012", + []interface{}{big.NewInt(0x12)}, + }, { + `[{"type":"function","name":"compareAndApprove","inputs":[{"type":"address"},{"type":"uint256"},{"type":"uint256"}]}]`, + "751e107900000000000000000000000000000133700000deadbeef00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + []interface{}{ + common.HexToAddress("0x00000133700000deadbeef000000000000000000"), + new(big.Int).SetBytes([]byte{0x00}), + big.NewInt(0x1), + }, + }, + } + for _, c := range testcases { + verify(t, c.jsondata, c.calldata, c.exp) + } + +} + +/* +func TestReflect(t *testing.T) { + a := big.NewInt(0) + b := new(big.Int).SetBytes([]byte{0x00}) + if !reflect.DeepEqual(a, b) { + t.Fatalf("Nope, %v != %v", a, b) + } +} +*/ + +func TestCalldataDecoding(t *testing.T) { + + // send(uint256) : a52c101e + // compareAndApprove(address,uint256,uint256) : 751e1079 + // issue(address[],uint256) : 42958b54 + jsondata := ` +[ + {"type":"function","name":"send","inputs":[{"name":"a","type":"uint256"}]}, + {"type":"function","name":"compareAndApprove","inputs":[{"name":"a","type":"address"},{"name":"a","type":"uint256"},{"name":"a","type":"uint256"}]}, + {"type":"function","name":"issue","inputs":[{"name":"a","type":"address[]"},{"name":"a","type":"uint256"}]}, + {"type":"function","name":"sam","inputs":[{"name":"a","type":"bytes"},{"name":"a","type":"bool"},{"name":"a","type":"uint256[]"}]} +]` + //Expected failures + for _, hexdata := range []string{ + "a52c101e00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + "a52c101e000000000000000000000000000000000000000000000000000000000000001200", + "a52c101e00000000000000000000000000000000000000000000000000000000000000", + "a52c101e", + "a52c10", + "", + // Too short + "751e10790000000000000000000000000000000000000000000000000000000000000012", + "751e1079FFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + //Not valid multiple of 32 + "deadbeef00000000000000000000000000000000000000000000000000000000000000", + //Too short 'issue' + "42958b5400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + // Too short compareAndApprove + "a52c101e00ff0000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000042", + // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + // contains a bool with illegal values + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + } { + _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) + if err == nil { + t.Errorf("Expected decoding to fail: %s", hexdata) + } + } + + //Expected success + for _, hexdata := range []string{ + // From https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI + "a5643bf20000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000464617665000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003", + "a52c101e0000000000000000000000000000000000000000000000000000000000000012", + "a52c101eFFffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "751e1079000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "42958b54" + + // start of dynamic type + "0000000000000000000000000000000000000000000000000000000000000040" + + //uint256 + "0000000000000000000000000000000000000000000000000000000000000001" + + // length of array + "0000000000000000000000000000000000000000000000000000000000000002" + + // array values + "000000000000000000000000000000000000000000000000000000000000dead" + + "000000000000000000000000000000000000000000000000000000000000beef", + } { + _, err := parseCallData(common.Hex2Bytes(hexdata), jsondata) + if err != nil { + t.Errorf("Unexpected failure on input %s:\n %v (%d bytes) ", hexdata, err, len(common.Hex2Bytes(hexdata))) + } + } +} + +func TestSelectorUnmarshalling(t *testing.T) { + var ( + db *AbiDb + err error + abistring []byte + abistruct abi.ABI + ) + + db, err = NewAbiDBFromFile("../../cmd/clef/4byte.json") + if err != nil { + t.Fatal(err) + } + fmt.Printf("DB size %v\n", db.Size()) + for id, selector := range db.db { + + abistring, err = MethodSelectorToAbi(selector) + if err != nil { + t.Error(err) + return + } + abistruct, err = abi.JSON(strings.NewReader(string(abistring))) + if err != nil { + t.Error(err) + return + } + m, err := abistruct.MethodById(common.Hex2Bytes(id[2:])) + if err != nil { + t.Error(err) + return + } + if m.Sig() != selector { + t.Errorf("Expected equality: %v != %v", m.Sig(), selector) + } + } + +} + +func TestCustomABI(t *testing.T) { + d, err := ioutil.TempDir("", "signer-4byte-test") + if err != nil { + t.Fatal(err) + } + filename := fmt.Sprintf("%s/4byte_custom.json", d) + abidb, err := NewAbiDBFromFiles("../../cmd/clef/4byte.json", filename) + if err != nil { + t.Fatal(err) + } + // Now we'll remove all existing signatures + abidb.db = make(map[string]string) + calldata := common.Hex2Bytes("a52c101edeadbeef") + _, err = abidb.LookupMethodSelector(calldata) + if err == nil { + t.Fatalf("Should not find a match on empty db") + } + if err = abidb.AddSignature("send(uint256)", calldata); err != nil { + t.Fatalf("Failed to save file: %v", err) + } + _, err = abidb.LookupMethodSelector(calldata) + if err != nil { + t.Fatalf("Should find a match for abi signature, got: %v", err) + } + //Check that it wrote to file + abidb2, err := NewAbiDBFromFile(filename) + if err != nil { + t.Fatalf("Failed to create new abidb: %v", err) + } + _, err = abidb2.LookupMethodSelector(calldata) + if err != nil { + t.Fatalf("Save failed: should find a match for abi signature after loading from disk") + } +} diff --git a/signer/core/api.go b/signer/core/api.go new file mode 100644 index 000000000000..1387041cc34b --- /dev/null +++ b/signer/core/api.go @@ -0,0 +1,500 @@ +// 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 . + +package core + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "math/big" + "reflect" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/usbwallet" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// ExternalAPI defines the external API through which signing requests are made. +type ExternalAPI interface { + // List available accounts + List(ctx context.Context) (Accounts, error) + // New request to create a new account + New(ctx context.Context) (accounts.Account, error) + // SignTransaction request to sign the specified transaction + SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) + // Sign - request to sign the given data (plus prefix) + Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) + // EcRecover - request to perform ecrecover + EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) + // Export - request to export an account + Export(ctx context.Context, addr common.Address) (json.RawMessage, error) + // Import - request to import an account + Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) +} + +// SignerUI specifies what method a UI needs to implement to be able to be used as a UI for the signer +type SignerUI interface { + // ApproveTx prompt the user for confirmation to request to sign Transaction + ApproveTx(request *SignTxRequest) (SignTxResponse, error) + // ApproveSignData prompt the user for confirmation to request to sign data + ApproveSignData(request *SignDataRequest) (SignDataResponse, error) + // ApproveExport prompt the user for confirmation to export encrypted Account json + ApproveExport(request *ExportRequest) (ExportResponse, error) + // ApproveImport prompt the user for confirmation to import Account json + ApproveImport(request *ImportRequest) (ImportResponse, error) + // ApproveListing prompt the user for confirmation to list accounts + // the list of accounts to list can be modified by the UI + ApproveListing(request *ListRequest) (ListResponse, error) + // ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller + ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) + // ShowError displays error message to user + ShowError(message string) + // ShowInfo displays info message to user + ShowInfo(message string) + // OnApprovedTx notifies the UI about a transaction having been successfully signed. + // This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient. + OnApprovedTx(tx ethapi.SignTransactionResult) + // OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version + // information + OnSignerStartup(info StartupInfo) +} + +// SignerAPI defines the actual implementation of ExternalAPI +type SignerAPI struct { + chainID *big.Int + am *accounts.Manager + UI SignerUI + validator *Validator +} + +// Metadata about a request +type Metadata struct { + Remote string `json:"remote"` + Local string `json:"local"` + Scheme string `json:"scheme"` +} + +// MetadataFromContext extracts Metadata from a given context.Context +func MetadataFromContext(ctx context.Context) Metadata { + m := Metadata{"NA", "NA", "NA"} // batman + + if v := ctx.Value("remote"); v != nil { + m.Remote = v.(string) + } + if v := ctx.Value("scheme"); v != nil { + m.Scheme = v.(string) + } + if v := ctx.Value("local"); v != nil { + m.Local = v.(string) + } + return m +} + +// String implements Stringer interface +func (m Metadata) String() string { + s, err := json.Marshal(m) + if err == nil { + return string(s) + } + return err.Error() +} + +// types for the requests/response types between signer and UI +type ( + // SignTxRequest contains info about a Transaction to sign + SignTxRequest struct { + Transaction SendTxArgs `json:"transaction"` + Callinfo []ValidationInfo `json:"call_info"` + Meta Metadata `json:"meta"` + } + // SignTxResponse result from SignTxRequest + SignTxResponse struct { + //The UI may make changes to the TX + Transaction SendTxArgs `json:"transaction"` + Approved bool `json:"approved"` + Password string `json:"password"` + } + // ExportRequest info about query to export accounts + ExportRequest struct { + Address common.Address `json:"address"` + Meta Metadata `json:"meta"` + } + // ExportResponse response to export-request + ExportResponse struct { + Approved bool `json:"approved"` + } + // ImportRequest info about request to import an Account + ImportRequest struct { + Meta Metadata `json:"meta"` + } + ImportResponse struct { + Approved bool `json:"approved"` + OldPassword string `json:"old_password"` + NewPassword string `json:"new_password"` + } + SignDataRequest struct { + Address common.MixedcaseAddress `json:"address"` + Rawdata hexutil.Bytes `json:"raw_data"` + Message string `json:"message"` + Hash hexutil.Bytes `json:"hash"` + Meta Metadata `json:"meta"` + } + SignDataResponse struct { + Approved bool `json:"approved"` + Password string + } + NewAccountRequest struct { + Meta Metadata `json:"meta"` + } + NewAccountResponse struct { + Approved bool `json:"approved"` + Password string `json:"password"` + } + ListRequest struct { + Accounts []Account `json:"accounts"` + Meta Metadata `json:"meta"` + } + ListResponse struct { + Accounts []Account `json:"accounts"` + } + Message struct { + Text string `json:"text"` + } + StartupInfo struct { + Info map[string]interface{} `json:"info"` + } +) + +var ErrRequestDenied = errors.New("Request denied") + +type errorWrapper struct { + msg string + err error +} + +func (ew errorWrapper) String() string { + return fmt.Sprintf("%s\n%s", ew.msg, ew.err) +} + +// NewSignerAPI creates a new API that can be used for Account management. +// ksLocation specifies the directory where to store the password protected private +// key that is generated when a new Account is created. +// noUSB disables USB support that is required to support hardware devices such as +// ledger and trezor. +func NewSignerAPI(chainID int64, ksLocation string, noUSB bool, ui SignerUI, abidb *AbiDb, lightKDF bool) *SignerAPI { + var ( + backends []accounts.Backend + n, p = keystore.StandardScryptN, keystore.StandardScryptP + ) + if lightKDF { + n, p = keystore.LightScryptN, keystore.LightScryptP + } + // support password based accounts + if len(ksLocation) > 0 { + backends = append(backends, keystore.NewKeyStore(ksLocation, n, p)) + } + if !noUSB { + // Start a USB hub for Ledger hardware wallets + if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil { + log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err)) + } else { + backends = append(backends, ledgerhub) + log.Debug("Ledger support enabled") + } + // Start a USB hub for Trezor hardware wallets + if trezorhub, err := usbwallet.NewTrezorHub(); err != nil { + log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err)) + } else { + backends = append(backends, trezorhub) + log.Debug("Trezor support enabled") + } + } + return &SignerAPI{big.NewInt(chainID), accounts.NewManager(backends...), ui, NewValidator(abidb)} +} + +// List returns the set of wallet this signer manages. Each wallet can contain +// multiple accounts. +func (api *SignerAPI) List(ctx context.Context) (Accounts, error) { + var accs []Account + for _, wallet := range api.am.Wallets() { + for _, acc := range wallet.Accounts() { + acc := Account{Typ: "Account", URL: wallet.URL(), Address: acc.Address} + accs = append(accs, acc) + } + } + result, err := api.UI.ApproveListing(&ListRequest{Accounts: accs, Meta: MetadataFromContext(ctx)}) + if err != nil { + return nil, err + } + if result.Accounts == nil { + return nil, ErrRequestDenied + + } + return result.Accounts, nil +} + +// New creates a new password protected Account. The private key is protected with +// the given password. Users are responsible to backup the private key that is stored +// in the keystore location thas was specified when this API was created. +func (api *SignerAPI) New(ctx context.Context) (accounts.Account, error) { + be := api.am.Backends(keystore.KeyStoreType) + if len(be) == 0 { + return accounts.Account{}, errors.New("password based accounts not supported") + } + resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}) + + if err != nil { + return accounts.Account{}, err + } + if !resp.Approved { + return accounts.Account{}, ErrRequestDenied + } + return be[0].(*keystore.KeyStore).NewAccount(resp.Password) +} + +// logDiff logs the difference between the incoming (original) transaction and the one returned from the signer. +// it also returns 'true' if the transaction was modified, to make it possible to configure the signer not to allow +// UI-modifications to requests +func logDiff(original *SignTxRequest, new *SignTxResponse) bool { + modified := false + if f0, f1 := original.Transaction.From, new.Transaction.From; !reflect.DeepEqual(f0, f1) { + log.Info("Sender-account changed by UI", "was", f0, "is", f1) + modified = true + } + if t0, t1 := original.Transaction.To, new.Transaction.To; !reflect.DeepEqual(t0, t1) { + log.Info("Recipient-account changed by UI", "was", t0, "is", t1) + modified = true + } + if g0, g1 := original.Transaction.Gas, new.Transaction.Gas; g0 != g1 { + modified = true + log.Info("Gas changed by UI", "was", g0, "is", g1) + } + if g0, g1 := big.Int(original.Transaction.GasPrice), big.Int(new.Transaction.GasPrice); g0.Cmp(&g1) != 0 { + modified = true + log.Info("GasPrice changed by UI", "was", g0, "is", g1) + } + if v0, v1 := big.Int(original.Transaction.Value), big.Int(new.Transaction.Value); v0.Cmp(&v1) != 0 { + modified = true + log.Info("Value changed by UI", "was", v0, "is", v1) + } + if d0, d1 := original.Transaction.Data, new.Transaction.Data; d0 != d1 { + d0s := "" + d1s := "" + if d0 != nil { + d0s = common.ToHex(*d0) + } + if d1 != nil { + d1s = common.ToHex(*d1) + } + if d1s != d0s { + modified = true + log.Info("Data changed by UI", "was", d0s, "is", d1s) + } + } + if n0, n1 := original.Transaction.Nonce, new.Transaction.Nonce; n0 != n1 { + modified = true + log.Info("Nonce changed by UI", "was", n0, "is", n1) + } + return modified +} + +// SignTransaction signs the given Transaction and returns it both as json and rlp-encoded form +func (api *SignerAPI) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { + var ( + err error + result SignTxResponse + ) + msgs, err := api.validator.ValidateTransaction(&args, methodSelector) + if err != nil { + return nil, err + } + + req := SignTxRequest{ + Transaction: args, + Meta: MetadataFromContext(ctx), + Callinfo: msgs.Messages, + } + // Process approval + result, err = api.UI.ApproveTx(&req) + if err != nil { + return nil, err + } + if !result.Approved { + return nil, ErrRequestDenied + } + // Log changes made by the UI to the signing-request + logDiff(&req, &result) + var ( + acc accounts.Account + wallet accounts.Wallet + ) + acc = accounts.Account{Address: result.Transaction.From.Address()} + wallet, err = api.am.Find(acc) + if err != nil { + return nil, err + } + // Convert fields into a real transaction + var unsignedTx = result.Transaction.toTransaction() + + // The one to sign is the one that was returned from the UI + signedTx, err := wallet.SignTxWithPassphrase(acc, result.Password, unsignedTx, api.chainID) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + + rlpdata, err := rlp.EncodeToBytes(signedTx) + response := ethapi.SignTransactionResult{Raw: rlpdata, Tx: signedTx} + + // Finally, send the signed tx to the UI + api.UI.OnApprovedTx(response) + // ...and to the external caller + return &response, nil + +} + +// Sign calculates an Ethereum ECDSA signature for: +// keccack256("\x19Ethereum Signed Message:\n" + len(message) + message)) +// +// Note, the produced signature conforms to the secp256k1 curve R, S and V values, +// where the V value will be 27 or 28 for legacy reasons. +// +// The key used to calculate the signature is decrypted with the given password. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign +func (api *SignerAPI) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + sighash, msg := SignHash(data) + // We make the request prior to looking up if we actually have the account, to prevent + // account-enumeration via the API + req := &SignDataRequest{Address: addr, Rawdata: data, Message: msg, Hash: sighash, Meta: MetadataFromContext(ctx)} + res, err := api.UI.ApproveSignData(req) + + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + account := accounts.Account{Address: addr.Address()} + wallet, err := api.am.Find(account) + if err != nil { + return nil, err + } + // Assemble sign the data with the wallet + signature, err := wallet.SignHashWithPassphrase(account, res.Password, sighash) + if err != nil { + api.UI.ShowError(err.Error()) + return nil, err + } + signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper + return signature, nil +} + +// EcRecover returns the address for the Account that was used to create the signature. +// Note, this function is compatible with eth_sign and personal_sign. As such it recovers +// the address of: +// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message}) +// addr = ecrecover(hash, signature) +// +// Note, the signature must conform to the secp256k1 curve R, S and V values, where +// the V value must be be 27 or 28 for legacy reasons. +// +// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover +func (api *SignerAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) { + if len(sig) != 65 { + return common.Address{}, fmt.Errorf("signature must be 65 bytes long") + } + if sig[64] != 27 && sig[64] != 28 { + return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)") + } + sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1 + hash, _ := SignHash(data) + rpk, err := crypto.Ecrecover(hash, sig) + if err != nil { + return common.Address{}, err + } + pubKey := crypto.ToECDSAPub(rpk) + recoveredAddr := crypto.PubkeyToAddress(*pubKey) + return recoveredAddr, nil +} + +// SignHash is a helper function that calculates a hash for the given message that can be +// safely used to calculate a signature from. +// +// The hash is calculated as +// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}). +// +// This gives context to the signed message and prevents signing of transactions. +func SignHash(data []byte) ([]byte, string) { + msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) + return crypto.Keccak256([]byte(msg)), msg +} + +// Export returns encrypted private key associated with the given address in web3 keystore format. +func (api *SignerAPI) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { + res, err := api.UI.ApproveExport(&ExportRequest{Address: addr, Meta: MetadataFromContext(ctx)}) + + if err != nil { + return nil, err + } + if !res.Approved { + return nil, ErrRequestDenied + } + // Look up the wallet containing the requested signer + wallet, err := api.am.Find(accounts.Account{Address: addr}) + if err != nil { + return nil, err + } + if wallet.URL().Scheme != keystore.KeyStoreScheme { + return nil, fmt.Errorf("Account is not a keystore-account") + } + return ioutil.ReadFile(wallet.URL().Path) +} + +// Imports tries to import the given keyJSON in the local keystore. The keyJSON data is expected to be +// in web3 keystore format. It will decrypt the keyJSON with the given passphrase and on successful +// decryption it will encrypt the key with the given newPassphrase and store it in the keystore. +func (api *SignerAPI) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) { + be := api.am.Backends(keystore.KeyStoreType) + + if len(be) == 0 { + return Account{}, errors.New("password based accounts not supported") + } + res, err := api.UI.ApproveImport(&ImportRequest{Meta: MetadataFromContext(ctx)}) + + if err != nil { + return Account{}, err + } + if !res.Approved { + return Account{}, ErrRequestDenied + } + acc, err := be[0].(*keystore.KeyStore).Import(keyJSON, res.OldPassword, res.NewPassword) + if err != nil { + api.UI.ShowError(err.Error()) + return Account{}, err + } + return Account{Typ: "Account", URL: acc.URL, Address: acc.Address}, nil +} diff --git a/signer/core/api_test.go b/signer/core/api_test.go new file mode 100644 index 000000000000..50ad02198753 --- /dev/null +++ b/signer/core/api_test.go @@ -0,0 +1,386 @@ +// 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 . +// +package core + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/rlp" +) + +//Used for testing +type HeadlessUI struct { + controller chan string +} + +func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) { +} + +func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + fmt.Printf("OnApproved called") +} + +func (ui *HeadlessUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { + + switch <-ui.controller { + case "Y": + return SignTxResponse{request.Transaction, true, <-ui.controller}, nil + case "M": //Modify + old := big.Int(request.Transaction.Value) + newVal := big.NewInt(0).Add(&old, big.NewInt(1)) + request.Transaction.Value = hexutil.Big(*newVal) + return SignTxResponse{request.Transaction, true, <-ui.controller}, nil + default: + return SignTxResponse{request.Transaction, false, ""}, nil + } +} +func (ui *HeadlessUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { + if "Y" == <-ui.controller { + return SignDataResponse{true, <-ui.controller}, nil + } + return SignDataResponse{false, ""}, nil +} +func (ui *HeadlessUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { + + return ExportResponse{<-ui.controller == "Y"}, nil + +} +func (ui *HeadlessUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { + + if "Y" == <-ui.controller { + return ImportResponse{true, <-ui.controller, <-ui.controller}, nil + } + return ImportResponse{false, "", ""}, nil +} +func (ui *HeadlessUI) ApproveListing(request *ListRequest) (ListResponse, error) { + + switch <-ui.controller { + case "A": + return ListResponse{request.Accounts}, nil + case "1": + l := make([]Account, 1) + l[0] = request.Accounts[1] + return ListResponse{l}, nil + default: + return ListResponse{nil}, nil + } +} +func (ui *HeadlessUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { + + if "Y" == <-ui.controller { + return NewAccountResponse{true, <-ui.controller}, nil + } + return NewAccountResponse{false, ""}, nil +} +func (ui *HeadlessUI) ShowError(message string) { + //stdout is used by communication + fmt.Fprint(os.Stderr, message) +} +func (ui *HeadlessUI) ShowInfo(message string) { + //stdout is used by communication + fmt.Fprint(os.Stderr, message) +} + +func tmpDirName(t *testing.T) string { + d, err := ioutil.TempDir("", "eth-keystore-test") + if err != nil { + t.Fatal(err) + } + d, err = filepath.EvalSymlinks(d) + if err != nil { + t.Fatal(err) + } + return d +} + +func setup(t *testing.T) (*SignerAPI, chan string) { + + controller := make(chan string, 10) + + db, err := NewAbiDBFromFile("../../cmd/clef/4byte.json") + if err != nil { + utils.Fatalf(err.Error()) + } + var ( + ui = &HeadlessUI{controller} + api = NewSignerAPI( + 1, + tmpDirName(t), + true, + ui, + db, + true) + ) + return api, controller +} +func createAccount(control chan string, api *SignerAPI, t *testing.T) { + + control <- "Y" + control <- "apassword" + _, err := api.New(context.Background()) + if err != nil { + t.Fatal(err) + } + // Some time to allow changes to propagate + time.Sleep(250 * time.Millisecond) +} +func failCreateAccount(control chan string, api *SignerAPI, t *testing.T) { + control <- "N" + acc, err := api.New(context.Background()) + if err != ErrRequestDenied { + t.Fatal(err) + } + if acc.Address != (common.Address{}) { + t.Fatal("Empty address should be returned") + } +} +func list(control chan string, api *SignerAPI, t *testing.T) []Account { + control <- "A" + list, err := api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + return list +} + +func TestNewAcc(t *testing.T) { + + api, control := setup(t) + verifyNum := func(num int) { + if list := list(control, api, t); len(list) != num { + t.Errorf("Expected %d accounts, got %d", num, len(list)) + } + } + // Testing create and create-deny + createAccount(control, api, t) + createAccount(control, api, t) + failCreateAccount(control, api, t) + failCreateAccount(control, api, t) + createAccount(control, api, t) + failCreateAccount(control, api, t) + createAccount(control, api, t) + failCreateAccount(control, api, t) + verifyNum(4) + + // Testing listing: + // Listing one Account + control <- "1" + list, err := api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + if len(list) != 1 { + t.Fatalf("List should only show one Account") + } + // Listing denied + control <- "Nope" + list, err = api.List(context.Background()) + if len(list) != 0 { + t.Fatalf("List should be empty") + } + if err != ErrRequestDenied { + t.Fatal("Expected deny") + } +} + +func TestSignData(t *testing.T) { + + api, control := setup(t) + //Create two accounts + createAccount(control, api, t) + createAccount(control, api, t) + control <- "1" + list, err := api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + a := common.NewMixedcaseAddress(list[0].Address) + + control <- "Y" + control <- "wrongpassword" + h, err := api.Sign(context.Background(), a, []byte("EHLO world")) + if h != nil { + t.Errorf("Expected nil-data, got %x", h) + } + if err != keystore.ErrDecrypt { + t.Errorf("Expected ErrLocked! %v", err) + } + + control <- "No way" + h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + if h != nil { + t.Errorf("Expected nil-data, got %x", h) + } + if err != ErrRequestDenied { + t.Errorf("Expected ErrRequestDenied! %v", err) + } + + control <- "Y" + control <- "apassword" + h, err = api.Sign(context.Background(), a, []byte("EHLO world")) + + if err != nil { + t.Fatal(err) + } + if h == nil || len(h) != 65 { + t.Errorf("Expected 65 byte signature (got %d bytes)", len(h)) + } +} +func mkTestTx(from common.MixedcaseAddress) SendTxArgs { + to := common.NewMixedcaseAddress(common.HexToAddress("0x1337")) + gas := hexutil.Uint64(21000) + gasPrice := (hexutil.Big)(*big.NewInt(2000000000)) + value := (hexutil.Big)(*big.NewInt(1e18)) + nonce := (hexutil.Uint64)(0) + data := hexutil.Bytes(common.Hex2Bytes("01020304050607080a")) + tx := SendTxArgs{ + From: from, + To: &to, + Gas: gas, + GasPrice: gasPrice, + Value: value, + Data: &data, + Nonce: nonce} + return tx +} + +func TestSignTx(t *testing.T) { + + var ( + list Accounts + res, res2 *ethapi.SignTransactionResult + err error + ) + + api, control := setup(t) + createAccount(control, api, t) + control <- "A" + list, err = api.List(context.Background()) + if err != nil { + t.Fatal(err) + } + a := common.NewMixedcaseAddress(list[0].Address) + + methodSig := "test(uint)" + tx := mkTestTx(a) + + control <- "Y" + control <- "wrongpassword" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) + if res != nil { + t.Errorf("Expected nil-response, got %v", res) + } + if err != keystore.ErrDecrypt { + t.Errorf("Expected ErrLocked! %v", err) + } + + control <- "No way" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) + if res != nil { + t.Errorf("Expected nil-response, got %v", res) + } + if err != ErrRequestDenied { + t.Errorf("Expected ErrRequestDenied! %v", err) + } + + control <- "Y" + control <- "apassword" + res, err = api.SignTransaction(context.Background(), tx, &methodSig) + + if err != nil { + t.Fatal(err) + } + parsedTx := &types.Transaction{} + rlp.Decode(bytes.NewReader(res.Raw), parsedTx) + //The tx should NOT be modified by the UI + if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { + t.Errorf("Expected value to be unchanged, expected %v got %v", tx.Value, parsedTx.Value()) + } + control <- "Y" + control <- "apassword" + + res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(res.Raw, res2.Raw) { + t.Error("Expected tx to be unmodified by UI") + } + + //The tx is modified by the UI + control <- "M" + control <- "apassword" + + res2, err = api.SignTransaction(context.Background(), tx, &methodSig) + if err != nil { + t.Fatal(err) + } + + parsedTx2 := &types.Transaction{} + rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) + //The tx should be modified by the UI + if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { + t.Errorf("Expected value to be unchanged, got %v", parsedTx.Value()) + } + + if bytes.Equal(res.Raw, res2.Raw) { + t.Error("Expected tx to be modified by UI") + } + +} + +/* +func TestAsyncronousResponses(t *testing.T){ + + //Set up one account + api, control := setup(t) + createAccount(control, api, t) + + // Two transactions, the second one with larger value than the first + tx1 := mkTestTx() + newVal := big.NewInt(0).Add((*big.Int) (tx1.Value), big.NewInt(1)) + tx2 := mkTestTx() + tx2.Value = (*hexutil.Big)(newVal) + + control <- "W" //wait + control <- "Y" // + control <- "apassword" + control <- "Y" // + control <- "apassword" + + var err error + + h1, err := api.SignTransaction(context.Background(), common.HexToAddress("1111"), tx1, nil) + h2, err := api.SignTransaction(context.Background(), common.HexToAddress("2222"), tx2, nil) + + + } +*/ diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go new file mode 100644 index 000000000000..d0ba733d2f38 --- /dev/null +++ b/signer/core/auditlog.go @@ -0,0 +1,110 @@ +// 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 . + +package core + +import ( + "context" + + "encoding/json" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" +) + +type AuditLogger struct { + log log.Logger + api ExternalAPI +} + +func (l *AuditLogger) List(ctx context.Context) (Accounts, error) { + l.log.Info("List", "type", "request", "metadata", MetadataFromContext(ctx).String()) + res, e := l.api.List(ctx) + + l.log.Info("List", "type", "response", "data", res.String()) + + return res, e +} + +func (l *AuditLogger) New(ctx context.Context) (accounts.Account, error) { + return l.api.New(ctx) +} + +func (l *AuditLogger) SignTransaction(ctx context.Context, args SendTxArgs, methodSelector *string) (*ethapi.SignTransactionResult, error) { + sel := "" + if methodSelector != nil { + sel = *methodSelector + } + l.log.Info("SignTransaction", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "tx", args.String(), + "methodSelector", sel) + + res, e := l.api.SignTransaction(ctx, args, methodSelector) + if res != nil { + l.log.Info("SignTransaction", "type", "response", "data", common.Bytes2Hex(res.Raw), "error", e) + } else { + l.log.Info("SignTransaction", "type", "response", "data", res, "error", e) + } + return res, e +} + +func (l *AuditLogger) Sign(ctx context.Context, addr common.MixedcaseAddress, data hexutil.Bytes) (hexutil.Bytes, error) { + l.log.Info("Sign", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.String(), "data", common.Bytes2Hex(data)) + b, e := l.api.Sign(ctx, addr, data) + l.log.Info("Sign", "type", "response", "data", common.Bytes2Hex(b), "error", e) + return b, e +} + +func (l *AuditLogger) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) { + l.log.Info("EcRecover", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "data", common.Bytes2Hex(data)) + a, e := l.api.EcRecover(ctx, data, sig) + l.log.Info("EcRecover", "type", "response", "addr", a.String(), "error", e) + return a, e +} + +func (l *AuditLogger) Export(ctx context.Context, addr common.Address) (json.RawMessage, error) { + l.log.Info("Export", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "addr", addr.Hex()) + j, e := l.api.Export(ctx, addr) + // In this case, we don't actually log the json-response, which may be extra sensitive + l.log.Info("Export", "type", "response", "json response size", len(j), "error", e) + return j, e +} + +func (l *AuditLogger) Import(ctx context.Context, keyJSON json.RawMessage) (Account, error) { + // Don't actually log the json contents + l.log.Info("Import", "type", "request", "metadata", MetadataFromContext(ctx).String(), + "keyJSON size", len(keyJSON)) + a, e := l.api.Import(ctx, keyJSON) + l.log.Info("Import", "type", "response", "addr", a.String(), "error", e) + return a, e +} + +func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) { + l := log.New("api", "signer") + handler, err := log.FileHandler(path, log.LogfmtFormat()) + if err != nil { + return nil, err + } + l.SetHandler(handler) + l.Info("Configured", "audit log", path) + return &AuditLogger{l, api}, nil +} diff --git a/signer/core/cliui.go b/signer/core/cliui.go new file mode 100644 index 000000000000..0d9b5f3d36d4 --- /dev/null +++ b/signer/core/cliui.go @@ -0,0 +1,247 @@ +// 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 . +package core + +import ( + "bufio" + "fmt" + "os" + "strings" + + "sync" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "golang.org/x/crypto/ssh/terminal" +) + +type CommandlineUI struct { + in *bufio.Reader + mu sync.Mutex +} + +func NewCommandlineUI() *CommandlineUI { + return &CommandlineUI{in: bufio.NewReader(os.Stdin)} +} + +// readString reads a single line from stdin, trimming if from spaces, enforcing +// non-emptyness. +func (ui *CommandlineUI) readString() string { + for { + fmt.Printf("> ") + text, err := ui.in.ReadString('\n') + if err != nil { + log.Crit("Failed to read user input", "err", err) + } + if text = strings.TrimSpace(text); text != "" { + return text + } + } +} + +// readPassword reads a single line from stdin, trimming it from the trailing new +// line and returns it. The input will not be echoed. +func (ui *CommandlineUI) readPassword() string { + fmt.Printf("Enter password to approve:\n") + fmt.Printf("> ") + + text, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + log.Crit("Failed to read password", "err", err) + } + fmt.Println() + fmt.Println("-----------------------") + return string(text) +} + +// readPassword reads a single line from stdin, trimming it from the trailing new +// line and returns it. The input will not be echoed. +func (ui *CommandlineUI) readPasswordText(inputstring string) string { + fmt.Printf("Enter %s:\n", inputstring) + fmt.Printf("> ") + text, err := terminal.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + log.Crit("Failed to read password", "err", err) + } + fmt.Println("-----------------------") + return string(text) +} + +// confirm returns true if user enters 'Yes', otherwise false +func (ui *CommandlineUI) confirm() bool { + fmt.Printf("Approve? [y/N]:\n") + if ui.readString() == "y" { + return true + } + fmt.Println("-----------------------") + return false +} + +func showMetadata(metadata Metadata) { + fmt.Printf("Request context:\n\t%v -> %v -> %v\n", metadata.Remote, metadata.Scheme, metadata.Local) +} + +// ApproveTx prompt the user for confirmation to request to sign Transaction +func (ui *CommandlineUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + weival := request.Transaction.Value.ToInt() + fmt.Printf("--------- Transaction request-------------\n") + if to := request.Transaction.To; to != nil { + fmt.Printf("to: %v\n", to.Original()) + if !to.ValidChecksum() { + fmt.Printf("\nWARNING: Invalid checksum on to-address!\n\n") + } + } else { + fmt.Printf("to: \n") + } + fmt.Printf("from: %v\n", request.Transaction.From.String()) + fmt.Printf("value: %v wei\n", weival) + if request.Transaction.Data != nil { + d := *request.Transaction.Data + if len(d) > 0 { + fmt.Printf("data: %v\n", common.Bytes2Hex(d)) + } + } + if request.Callinfo != nil { + fmt.Printf("\nTransaction validation:\n") + for _, m := range request.Callinfo { + fmt.Printf(" * %s : %s", m.Typ, m.Message) + } + fmt.Println() + + } + fmt.Printf("\n") + showMetadata(request.Meta) + fmt.Printf("-------------------------------------------\n") + if !ui.confirm() { + return SignTxResponse{request.Transaction, false, ""}, nil + } + return SignTxResponse{request.Transaction, true, ui.readPassword()}, nil +} + +// ApproveSignData prompt the user for confirmation to request to sign data +func (ui *CommandlineUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- Sign data request--------------\n") + fmt.Printf("Account: %s\n", request.Address.String()) + fmt.Printf("message: \n%q\n", request.Message) + fmt.Printf("raw data: \n%v\n", request.Rawdata) + fmt.Printf("message hash: %v\n", request.Hash) + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + if !ui.confirm() { + return SignDataResponse{false, ""}, nil + } + return SignDataResponse{true, ui.readPassword()}, nil +} + +// ApproveExport prompt the user for confirmation to export encrypted Account json +func (ui *CommandlineUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- Export Account request--------------\n") + fmt.Printf("A request has been made to export the (encrypted) keyfile\n") + fmt.Printf("Approving this operation means that the caller obtains the (encrypted) contents\n") + fmt.Printf("\n") + fmt.Printf("Account: %x\n", request.Address) + //fmt.Printf("keyfile: \n%v\n", request.file) + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + return ExportResponse{ui.confirm()}, nil +} + +// ApproveImport prompt the user for confirmation to import Account json +func (ui *CommandlineUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- Import Account request--------------\n") + fmt.Printf("A request has been made to import an encrypted keyfile\n") + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + if !ui.confirm() { + return ImportResponse{false, "", ""}, nil + } + return ImportResponse{true, ui.readPasswordText("Old password"), ui.readPasswordText("New password")}, nil +} + +// ApproveListing prompt the user for confirmation to list accounts +// the list of accounts to list can be modified by the UI +func (ui *CommandlineUI) ApproveListing(request *ListRequest) (ListResponse, error) { + + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- List Account request--------------\n") + fmt.Printf("A request has been made to list all accounts. \n") + fmt.Printf("You can select which accounts the caller can see\n") + for _, account := range request.Accounts { + fmt.Printf("\t[x] %v\n", account.Address.Hex()) + } + fmt.Printf("-------------------------------------------\n") + showMetadata(request.Meta) + if !ui.confirm() { + return ListResponse{nil}, nil + } + return ListResponse{request.Accounts}, nil +} + +// ApproveNewAccount prompt the user for confirmation to create new Account, and reveal to caller +func (ui *CommandlineUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { + + ui.mu.Lock() + defer ui.mu.Unlock() + + fmt.Printf("-------- New Account request--------------\n") + fmt.Printf("A request has been made to create a new. \n") + fmt.Printf("Approving this operation means that a new Account is created,\n") + fmt.Printf("and the address show to the caller\n") + showMetadata(request.Meta) + if !ui.confirm() { + return NewAccountResponse{false, ""}, nil + } + return NewAccountResponse{true, ui.readPassword()}, nil +} + +// ShowError displays error message to user +func (ui *CommandlineUI) ShowError(message string) { + + fmt.Printf("ERROR: %v\n", message) +} + +// ShowInfo displays info message to user +func (ui *CommandlineUI) ShowInfo(message string) { + fmt.Printf("Info: %v\n", message) +} + +func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + fmt.Printf("Transaction signed:\n ") + spew.Dump(tx.Tx) +} + +func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) { + + fmt.Printf("------- Signer info -------\n") + for k, v := range info.Info { + fmt.Printf("* %v : %v\n", k, v) + } +} diff --git a/signer/core/stdioui.go b/signer/core/stdioui.go new file mode 100644 index 000000000000..5640ed03bd8f --- /dev/null +++ b/signer/core/stdioui.go @@ -0,0 +1,113 @@ +// 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 . +// + +package core + +import ( + "context" + "sync" + + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +type StdIOUI struct { + client rpc.Client + mu sync.Mutex +} + +func NewStdIOUI() *StdIOUI { + log.Info("NewStdIOUI") + client, err := rpc.DialContext(context.Background(), "stdio://") + if err != nil { + log.Crit("Could not create stdio client", "err", err) + } + return &StdIOUI{client: *client} +} + +// dispatch sends a request over the stdio +func (ui *StdIOUI) dispatch(serviceMethod string, args interface{}, reply interface{}) error { + err := ui.client.Call(&reply, serviceMethod, args) + if err != nil { + log.Info("Error", "exc", err.Error()) + } + return err +} + +func (ui *StdIOUI) ApproveTx(request *SignTxRequest) (SignTxResponse, error) { + var result SignTxResponse + err := ui.dispatch("ApproveTx", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveSignData(request *SignDataRequest) (SignDataResponse, error) { + var result SignDataResponse + err := ui.dispatch("ApproveSignData", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveExport(request *ExportRequest) (ExportResponse, error) { + var result ExportResponse + err := ui.dispatch("ApproveExport", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveImport(request *ImportRequest) (ImportResponse, error) { + var result ImportResponse + err := ui.dispatch("ApproveImport", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveListing(request *ListRequest) (ListResponse, error) { + var result ListResponse + err := ui.dispatch("ApproveListing", request, &result) + return result, err +} + +func (ui *StdIOUI) ApproveNewAccount(request *NewAccountRequest) (NewAccountResponse, error) { + var result NewAccountResponse + err := ui.dispatch("ApproveNewAccount", request, &result) + return result, err +} + +func (ui *StdIOUI) ShowError(message string) { + err := ui.dispatch("ShowError", &Message{message}, nil) + if err != nil { + log.Info("Error calling 'ShowError'", "exc", err.Error(), "msg", message) + } +} + +func (ui *StdIOUI) ShowInfo(message string) { + err := ui.dispatch("ShowInfo", Message{message}, nil) + if err != nil { + log.Info("Error calling 'ShowInfo'", "exc", err.Error(), "msg", message) + } +} +func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) { + err := ui.dispatch("OnApprovedTx", tx, nil) + if err != nil { + log.Info("Error calling 'OnApprovedTx'", "exc", err.Error(), "tx", tx) + } +} + +func (ui *StdIOUI) OnSignerStartup(info StartupInfo) { + err := ui.dispatch("OnSignerStartup", info, nil) + if err != nil { + log.Info("Error calling 'OnSignerStartup'", "exc", err.Error(), "info", info) + } +} diff --git a/signer/core/types.go b/signer/core/types.go new file mode 100644 index 000000000000..8386bd44e798 --- /dev/null +++ b/signer/core/types.go @@ -0,0 +1,95 @@ +// 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 . + +package core + +import ( + "encoding/json" + "strings" + + "math/big" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +type Accounts []Account + +func (as Accounts) String() string { + var output []string + for _, a := range as { + output = append(output, a.String()) + } + return strings.Join(output, "\n") +} + +type Account struct { + Typ string `json:"type"` + URL accounts.URL `json:"url"` + Address common.Address `json:"address"` +} + +func (a Account) String() string { + s, err := json.Marshal(a) + if err == nil { + return string(s) + } + return err.Error() +} + +type ValidationInfo struct { + Typ string `json:"type"` + Message string `json:"message"` +} +type ValidationMessages struct { + Messages []ValidationInfo +} + +// SendTxArgs represents the arguments to submit a transaction +type SendTxArgs struct { + From common.MixedcaseAddress `json:"from"` + To *common.MixedcaseAddress `json:"to"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice hexutil.Big `json:"gasPrice"` + Value hexutil.Big `json:"value"` + Nonce hexutil.Uint64 `json:"nonce"` + // We accept "data" and "input" for backwards-compatibility reasons. + Data *hexutil.Bytes `json:"data"` + Input *hexutil.Bytes `json:"input"` +} + +func (t SendTxArgs) String() string { + s, err := json.Marshal(t) + if err == nil { + return string(s) + } + return err.Error() +} + +func (args *SendTxArgs) toTransaction() *types.Transaction { + var input []byte + if args.Data != nil { + input = *args.Data + } else if args.Input != nil { + input = *args.Input + } + if args.To == nil { + return types.NewContractCreation(uint64(args.Nonce), (*big.Int)(&args.Value), uint64(args.Gas), (*big.Int)(&args.GasPrice), input) + } + return types.NewTransaction(uint64(args.Nonce), args.To.Address(), (*big.Int)(&args.Value), (uint64)(args.Gas), (*big.Int)(&args.GasPrice), input) +} diff --git a/signer/core/validation.go b/signer/core/validation.go new file mode 100644 index 000000000000..97bb3b685098 --- /dev/null +++ b/signer/core/validation.go @@ -0,0 +1,163 @@ +// 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 . + +package core + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// The validation package contains validation checks for transactions +// - ABI-data validation +// - Transaction semantics validation +// The package provides warnings for typical pitfalls + +func (vs *ValidationMessages) crit(msg string) { + vs.Messages = append(vs.Messages, ValidationInfo{"CRITICAL", msg}) +} +func (vs *ValidationMessages) warn(msg string) { + vs.Messages = append(vs.Messages, ValidationInfo{"WARNING", msg}) +} +func (vs *ValidationMessages) info(msg string) { + vs.Messages = append(vs.Messages, ValidationInfo{"Info", msg}) +} + +type Validator struct { + db *AbiDb +} + +func NewValidator(db *AbiDb) *Validator { + return &Validator{db} +} +func testSelector(selector string, data []byte) (*decodedCallData, error) { + if selector == "" { + return nil, fmt.Errorf("selector not found") + } + abiData, err := MethodSelectorToAbi(selector) + if err != nil { + return nil, err + } + info, err := parseCallData(data, string(abiData)) + if err != nil { + return nil, err + } + return info, nil + +} + +// validateCallData checks if the ABI-data + methodselector (if given) can be parsed and seems to match +func (v *Validator) validateCallData(msgs *ValidationMessages, data []byte, methodSelector *string) { + if len(data) == 0 { + return + } + if len(data) < 4 { + msgs.warn("Tx contains data which is not valid ABI") + return + } + var ( + info *decodedCallData + err error + ) + // Check the provided one + if methodSelector != nil { + info, err = testSelector(*methodSelector, data) + if err != nil { + msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err)) + } else { + msgs.info(info.String()) + //Successfull match. add to db if not there already (ignore errors there) + v.db.AddSignature(*methodSelector, data[:4]) + } + return + } + // Check the db + selector, err := v.db.LookupMethodSelector(data[:4]) + if err != nil { + msgs.warn(fmt.Sprintf("Tx contains data, but the ABI signature could not be found: %v", err)) + return + } + info, err = testSelector(selector, data) + if err != nil { + msgs.warn(fmt.Sprintf("Tx contains data, but provided ABI signature could not be matched: %v", err)) + } else { + msgs.info(info.String()) + } +} + +// validateSemantics checks if the transactions 'makes sense', and generate warnings for a couple of typical scenarios +func (v *Validator) validate(msgs *ValidationMessages, txargs *SendTxArgs, methodSelector *string) error { + // Prevent accidental erroneous usage of both 'input' and 'data' + if txargs.Data != nil && txargs.Input != nil && !bytes.Equal(*txargs.Data, *txargs.Input) { + // This is a showstopper + return errors.New(`Ambiguous request: both "data" and "input" are set and are not identical`) + } + var ( + data []byte + ) + // Place data on 'data', and nil 'input' + if txargs.Input != nil { + txargs.Data = txargs.Input + txargs.Input = nil + } + if txargs.Data != nil { + data = *txargs.Data + } + + if txargs.To == nil { + //Contract creation should contain sufficient data to deploy a contract + // A typical error is omitting sender due to some quirk in the javascript call + // e.g. https://github.com/ethereum/go-ethereum/issues/16106 + if len(data) == 0 { + if txargs.Value.ToInt().Cmp(big.NewInt(0)) > 0 { + // Sending ether into black hole + return errors.New(`Tx will create contract with value but empty code!`) + } + // No value submitted at least + msgs.crit("Tx will create contract with empty code!") + } else if len(data) < 40 { //Arbitrary limit + msgs.warn(fmt.Sprintf("Tx will will create contract, but payload is suspiciously small (%d b)", len(data))) + } + // methodSelector should be nil for contract creation + if methodSelector != nil { + msgs.warn("Tx will create contract, but method selector supplied; indicating intent to call a method.") + } + + } else { + if !txargs.To.ValidChecksum() { + msgs.warn("Invalid checksum on to-address") + } + // Normal transaction + if bytes.Equal(txargs.To.Address().Bytes(), common.Address{}.Bytes()) { + // Sending to 0 + msgs.crit("Tx destination is the zero address!") + } + // Validate calldata + v.validateCallData(msgs, data, methodSelector) + } + return nil +} + +// ValidateTransaction does a number of checks on the supplied transaction, and returns either a list of warnings, +// or an error, indicating that the transaction should be immediately rejected +func (v *Validator) ValidateTransaction(txArgs *SendTxArgs, methodSelector *string) (*ValidationMessages, error) { + msgs := &ValidationMessages{} + return msgs, v.validate(msgs, txArgs, methodSelector) +} diff --git a/signer/core/validation_test.go b/signer/core/validation_test.go new file mode 100644 index 000000000000..2b33a8630b4b --- /dev/null +++ b/signer/core/validation_test.go @@ -0,0 +1,139 @@ +// 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 . + +package core + +import ( + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func hexAddr(a string) common.Address { return common.BytesToAddress(common.FromHex(a)) } +func mixAddr(a string) (*common.MixedcaseAddress, error) { + return common.NewMixedcaseAddressFromString(a) +} +func toHexBig(h string) hexutil.Big { + b := big.NewInt(0).SetBytes(common.FromHex(h)) + return hexutil.Big(*b) +} +func toHexUint(h string) hexutil.Uint64 { + b := big.NewInt(0).SetBytes(common.FromHex(h)) + return hexutil.Uint64(b.Uint64()) +} +func dummyTxArgs(t txtestcase) *SendTxArgs { + to, _ := mixAddr(t.to) + from, _ := mixAddr(t.from) + n := toHexUint(t.n) + gas := toHexUint(t.g) + gasPrice := toHexBig(t.gp) + value := toHexBig(t.value) + var ( + data, input *hexutil.Bytes + ) + if t.d != "" { + a := hexutil.Bytes(common.FromHex(t.d)) + data = &a + } + if t.i != "" { + a := hexutil.Bytes(common.FromHex(t.i)) + input = &a + + } + return &SendTxArgs{ + From: *from, + To: to, + Value: value, + Nonce: n, + GasPrice: gasPrice, + Gas: gas, + Data: data, + Input: input, + } +} + +type txtestcase struct { + from, to, n, g, gp, value, d, i string + expectErr bool + numMessages int +} + +func TestValidator(t *testing.T) { + var ( + // use empty db, there are other tests for the abi-specific stuff + db, _ = NewEmptyAbiDB() + v = NewValidator(db) + ) + testcases := []txtestcase{ + // Invalid to checksum + {from: "000000000000000000000000000000000000dead", to: "000000000000000000000000000000000000dead", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1}, + // valid 0x000000000000000000000000000000000000dEaD + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 0}, + // conflicting input and data + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", i: "0x02", expectErr: true}, + // Data can't be parsed + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x0102", numMessages: 1}, + // Data (on Input) can't be parsed + {from: "000000000000000000000000000000000000dead", to: "0x000000000000000000000000000000000000dEaD", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", i: "0x0102", numMessages: 1}, + // Send to 0 + {from: "000000000000000000000000000000000000dead", to: "0x0000000000000000000000000000000000000000", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", numMessages: 1}, + // Create empty contract (no value) + {from: "000000000000000000000000000000000000dead", to: "", + n: "0x01", g: "0x20", gp: "0x40", value: "0x00", numMessages: 1}, + // Create empty contract (with value) + {from: "000000000000000000000000000000000000dead", to: "", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", expectErr: true}, + // Small payload for create + {from: "000000000000000000000000000000000000dead", to: "", + n: "0x01", g: "0x20", gp: "0x40", value: "0x01", d: "0x01", numMessages: 1}, + } + for i, test := range testcases { + msgs, err := v.ValidateTransaction(dummyTxArgs(test), nil) + if err == nil && test.expectErr { + t.Errorf("Test %d, expected error", i) + for _, msg := range msgs.Messages { + fmt.Printf("* %s: %s\n", msg.Typ, msg.Message) + } + } + if err != nil && !test.expectErr { + t.Errorf("Test %d, unexpected error: %v", i, err) + } + if err == nil { + got := len(msgs.Messages) + if got != test.numMessages { + for _, msg := range msgs.Messages { + fmt.Printf("* %s: %s\n", msg.Typ, msg.Message) + } + t.Errorf("Test %d, expected %d messages, got %d", i, test.numMessages, got) + } else { + //Debug printout, remove later + for _, msg := range msgs.Messages { + fmt.Printf("* [%d] %s: %s\n", i, msg.Typ, msg.Message) + } + fmt.Println() + } + } + } +} diff --git a/signer/rules/deps/bignumber.js b/signer/rules/deps/bignumber.js new file mode 100644 index 000000000000..17c8851e24eb --- /dev/null +++ b/signer/rules/deps/bignumber.js @@ -0,0 +1,4 @@ +/* bignumber.js v2.0.3 https://github.com/MikeMcl/bignumber.js/LICENCE */ +/* modified by zelig to fix https://github.com/robertkrimen/otto#regular-expression-incompatibility */ +!function(e){"use strict";function n(e){function a(e,n){var t,r,i,o,u,s,f=this;if(!(f instanceof a))return j&&L(26,"constructor call without new",e),new a(e,n);if(null!=n&&H(n,2,64,M,"base")){if(n=0|n,s=e+"",10==n)return f=new a(e instanceof a?e:s),U(f,P+f.e+1,k);if((o="number"==typeof e)&&0*e!=0||!new RegExp("^-?"+(t="["+O.slice(0,n)+"]+")+"(?:\\."+t+")?$",37>n?"i":"").test(s))return g(f,s,o,n);o?(f.s=0>1/e?(s=s.slice(1),-1):1,j&&s.replace(/^0\.0*|\./,"").length>15&&L(M,b,e),o=!1):f.s=45===s.charCodeAt(0)?(s=s.slice(1),-1):1,s=D(s,10,n,f.s)}else{if(e instanceof a)return f.s=e.s,f.e=e.e,f.c=(e=e.c)?e.slice():e,void(M=0);if((o="number"==typeof e)&&0*e==0){if(f.s=0>1/e?(e=-e,-1):1,e===~~e){for(r=0,i=e;i>=10;i/=10,r++);return f.e=r,f.c=[e],void(M=0)}s=e+""}else{if(!p.test(s=e+""))return g(f,s,o);f.s=45===s.charCodeAt(0)?(s=s.slice(1),-1):1}}for((r=s.indexOf("."))>-1&&(s=s.replace(".","")),(i=s.search(/e/i))>0?(0>r&&(r=i),r+=+s.slice(i+1),s=s.substring(0,i)):0>r&&(r=s.length),i=0;48===s.charCodeAt(i);i++);for(u=s.length;48===s.charCodeAt(--u););if(s=s.slice(i,u+1))if(u=s.length,o&&j&&u>15&&L(M,b,f.s*e),r=r-i-1,r>z)f.c=f.e=null;else if(G>r)f.c=[f.e=0];else{if(f.e=r,f.c=[],i=(r+1)%y,0>r&&(i+=y),u>i){for(i&&f.c.push(+s.slice(0,i)),u-=y;u>i;)f.c.push(+s.slice(i,i+=y));s=s.slice(i),i=y-s.length}else i-=u;for(;i--;s+="0");f.c.push(+s)}else f.c=[f.e=0];M=0}function D(e,n,t,i){var o,u,f,c,h,g,p,d=e.indexOf("."),m=P,w=k;for(37>t&&(e=e.toLowerCase()),d>=0&&(f=J,J=0,e=e.replace(".",""),p=new a(t),h=p.pow(e.length-d),J=f,p.c=s(l(r(h.c),h.e),10,n),p.e=p.c.length),g=s(e,t,n),u=f=g.length;0==g[--f];g.pop());if(!g[0])return"0";if(0>d?--u:(h.c=g,h.e=u,h.s=i,h=C(h,p,m,w,n),g=h.c,c=h.r,u=h.e),o=u+m+1,d=g[o],f=n/2,c=c||0>o||null!=g[o+1],c=4>w?(null!=d||c)&&(0==w||w==(h.s<0?3:2)):d>f||d==f&&(4==w||c||6==w&&1&g[o-1]||w==(h.s<0?8:7)),1>o||!g[0])e=c?l("1",-m):"0";else{if(g.length=o,c)for(--n;++g[--o]>n;)g[o]=0,o||(++u,g.unshift(1));for(f=g.length;!g[--f];);for(d=0,e="";f>=d;e+=O.charAt(g[d++]));e=l(e,u)}return e}function _(e,n,t,i){var o,u,s,c,h;if(t=null!=t&&H(t,0,8,i,v)?0|t:k,!e.c)return e.toString();if(o=e.c[0],s=e.e,null==n)h=r(e.c),h=19==i||24==i&&B>=s?f(h,s):l(h,s);else if(e=U(new a(e),n,t),u=e.e,h=r(e.c),c=h.length,19==i||24==i&&(u>=n||B>=u)){for(;n>c;h+="0",c++);h=f(h,u)}else if(n-=s,h=l(h,u),u+1>c){if(--n>0)for(h+=".";n--;h+="0");}else if(n+=u-c,n>0)for(u+1==c&&(h+=".");n--;h+="0");return e.s<0&&o?"-"+h:h}function x(e,n){var t,r,i=0;for(u(e[0])&&(e=e[0]),t=new a(e[0]);++ie||e>t||e!=c(e))&&L(r,(i||"decimal places")+(n>e||e>t?" out of range":" not an integer"),e),!0}function I(e,n,t){for(var r=1,i=n.length;!n[--i];n.pop());for(i=n[0];i>=10;i/=10,r++);return(t=r+t*y-1)>z?e.c=e.e=null:G>t?e.c=[e.e=0]:(e.e=t,e.c=n),e}function L(e,n,t){var r=new Error(["new BigNumber","cmp","config","div","divToInt","eq","gt","gte","lt","lte","minus","mod","plus","precision","random","round","shift","times","toDigits","toExponential","toFixed","toFormat","toFraction","pow","toPrecision","toString","BigNumber"][e]+"() "+n+": "+t);throw r.name="BigNumber Error",M=0,r}function U(e,n,t,r){var i,o,u,s,f,l,c,a=e.c,h=R;if(a){e:{for(i=1,s=a[0];s>=10;s/=10,i++);if(o=n-i,0>o)o+=y,u=n,f=a[l=0],c=f/h[i-u-1]%10|0;else if(l=d((o+1)/y),l>=a.length){if(!r)break e;for(;a.length<=l;a.push(0));f=c=0,i=1,o%=y,u=o-y+1}else{for(f=s=a[l],i=1;s>=10;s/=10,i++);o%=y,u=o-y+i,c=0>u?0:f/h[i-u-1]%10|0}if(r=r||0>n||null!=a[l+1]||(0>u?f:f%h[i-u-1]),r=4>t?(c||r)&&(0==t||t==(e.s<0?3:2)):c>5||5==c&&(4==t||r||6==t&&(o>0?u>0?f/h[i-u]:0:a[l-1])%10&1||t==(e.s<0?8:7)),1>n||!a[0])return a.length=0,r?(n-=e.e+1,a[0]=h[n%y],e.e=-n||0):a[0]=e.e=0,e;if(0==o?(a.length=l,s=1,l--):(a.length=l+1,s=h[y-o],a[l]=u>0?m(f/h[i-u]%h[u])*s:0),r)for(;;){if(0==l){for(o=1,u=a[0];u>=10;u/=10,o++);for(u=a[0]+=s,s=1;u>=10;u/=10,s++);o!=s&&(e.e++,a[0]==N&&(a[0]=1));break}if(a[l]+=s,a[l]!=N)break;a[l--]=0,s=1}for(o=a.length;0===a[--o];a.pop());}e.e>z?e.c=e.e=null:e.et?null!=(e=i[t++]):void 0};return f(n="DECIMAL_PLACES")&&H(e,0,E,2,n)&&(P=0|e),r[n]=P,f(n="ROUNDING_MODE")&&H(e,0,8,2,n)&&(k=0|e),r[n]=k,f(n="EXPONENTIAL_AT")&&(u(e)?H(e[0],-E,0,2,n)&&H(e[1],0,E,2,n)&&(B=0|e[0],$=0|e[1]):H(e,-E,E,2,n)&&(B=-($=0|(0>e?-e:e)))),r[n]=[B,$],f(n="RANGE")&&(u(e)?H(e[0],-E,-1,2,n)&&H(e[1],1,E,2,n)&&(G=0|e[0],z=0|e[1]):H(e,-E,E,2,n)&&(0|e?G=-(z=0|(0>e?-e:e)):j&&L(2,n+" cannot be zero",e))),r[n]=[G,z],f(n="ERRORS")&&(e===!!e||1===e||0===e?(M=0,H=(j=!!e)?F:o):j&&L(2,n+w,e)),r[n]=j,f(n="CRYPTO")&&(e===!!e||1===e||0===e?(V=!(!e||!h||"object"!=typeof h),e&&!V&&j&&L(2,"crypto unavailable",h)):j&&L(2,n+w,e)),r[n]=V,f(n="MODULO_MODE")&&H(e,0,9,2,n)&&(W=0|e),r[n]=W,f(n="POW_PRECISION")&&H(e,0,E,2,n)&&(J=0|e),r[n]=J,f(n="FORMAT")&&("object"==typeof e?X=e:j&&L(2,n+" not an object",e)),r[n]=X,r},a.max=function(){return x(arguments,T.lt)},a.min=function(){return x(arguments,T.gt)},a.random=function(){var e=9007199254740992,n=Math.random()*e&2097151?function(){return m(Math.random()*e)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)};return function(e){var t,r,i,o,u,s=0,f=[],l=new a(q);if(e=null!=e&&H(e,0,E,14)?0|e:P,o=d(e/y),V)if(h&&h.getRandomValues){for(t=h.getRandomValues(new Uint32Array(o*=2));o>s;)u=131072*t[s]+(t[s+1]>>>11),u>=9e15?(r=h.getRandomValues(new Uint32Array(2)),t[s]=r[0],t[s+1]=r[1]):(f.push(u%1e14),s+=2);s=o/2}else if(h&&h.randomBytes){for(t=h.randomBytes(o*=7);o>s;)u=281474976710656*(31&t[s])+1099511627776*t[s+1]+4294967296*t[s+2]+16777216*t[s+3]+(t[s+4]<<16)+(t[s+5]<<8)+t[s+6],u>=9e15?h.randomBytes(7).copy(t,s):(f.push(u%1e14),s+=7);s=o/7}else j&&L(14,"crypto unavailable",h);if(!s)for(;o>s;)u=n(),9e15>u&&(f[s++]=u%1e14);for(o=f[--s],e%=y,o&&e&&(u=R[y-e],f[s]=m(o/u)*u);0===f[s];f.pop(),s--);if(0>s)f=[i=0];else{for(i=-1;0===f[0];f.shift(),i-=y);for(s=1,u=f[0];u>=10;u/=10,s++);y>s&&(i-=y-s)}return l.e=i,l.c=f,l}}(),C=function(){function e(e,n,t){var r,i,o,u,s=0,f=e.length,l=n%A,c=n/A|0;for(e=e.slice();f--;)o=e[f]%A,u=e[f]/A|0,r=c*o+u*l,i=l*o+r%A*A+s,s=(i/t|0)+(r/A|0)+c*u,e[f]=i%t;return s&&e.unshift(s),e}function n(e,n,t,r){var i,o;if(t!=r)o=t>r?1:-1;else for(i=o=0;t>i;i++)if(e[i]!=n[i]){o=e[i]>n[i]?1:-1;break}return o}function r(e,n,t,r){for(var i=0;t--;)e[t]-=i,i=e[t]1;e.shift());}return function(i,o,u,s,f){var l,c,h,g,p,d,w,v,b,O,S,R,A,E,D,_,x,F=i.s==o.s?1:-1,I=i.c,L=o.c;if(!(I&&I[0]&&L&&L[0]))return new a(i.s&&o.s&&(I?!L||I[0]!=L[0]:L)?I&&0==I[0]||!L?0*F:F/0:0/0);for(v=new a(F),b=v.c=[],c=i.e-o.e,F=u+c+1,f||(f=N,c=t(i.e/y)-t(o.e/y),F=F/y|0),h=0;L[h]==(I[h]||0);h++);if(L[h]>(I[h]||0)&&c--,0>F)b.push(1),g=!0;else{for(E=I.length,_=L.length,h=0,F+=2,p=m(f/(L[0]+1)),p>1&&(L=e(L,p,f),I=e(I,p,f),_=L.length,E=I.length),A=_,O=I.slice(0,_),S=O.length;_>S;O[S++]=0);x=L.slice(),x.unshift(0),D=L[0],L[1]>=f/2&&D++;do p=0,l=n(L,O,_,S),0>l?(R=O[0],_!=S&&(R=R*f+(O[1]||0)),p=m(R/D),p>1?(p>=f&&(p=f-1),d=e(L,p,f),w=d.length,S=O.length,l=n(d,O,w,S),1==l&&(p--,r(d,w>_?x:L,w,f))):(0==p&&(l=p=1),d=L.slice()),w=d.length,S>w&&d.unshift(0),r(O,d,S,f),-1==l&&(S=O.length,l=n(L,O,_,S),1>l&&(p++,r(O,S>_?x:L,S,f))),S=O.length):0===l&&(p++,O=[0]),b[h++]=p,l&&O[0]?O[S++]=I[A]||0:(O=[I[A]],S=1);while((A++=10;F/=10,h++);U(v,u+(v.e=h+c*y-1)+1,s,g)}else v.e=c,v.r=+g;return v}}(),g=function(){var e=/^(-?)0([xbo])(\w[\w.]*$)/i,n=/^([^.]+)\.$/,t=/^\.([^.]+)$/,r=/^-?(Infinity|NaN)$/,i=/^\s*\+([\w.])|^\s+|\s+$/g;return function(o,u,s,f){var l,c=s?u:u.replace(i,"$1");if(r.test(c))o.s=isNaN(c)?null:0>c?-1:1;else{if(!s&&(c=c.replace(e,function(e,n,t){return l="x"==(t=t.toLowerCase())?16:"b"==t?2:8,f&&f!=l?e:n}),f&&(l=f,c=c.replace(n,"$1").replace(t,"0.$1")),u!=c))return new a(c,l);j&&L(M,"not a"+(f?" base "+f:"")+" number",u),o.s=null}o.c=o.e=null,M=0}}(),T.absoluteValue=T.abs=function(){var e=new a(this);return e.s<0&&(e.s=1),e},T.ceil=function(){return U(new a(this),this.e+1,2)},T.comparedTo=T.cmp=function(e,n){return M=1,i(this,new a(e,n))},T.decimalPlaces=T.dp=function(){var e,n,r=this.c;if(!r)return null;if(e=((n=r.length-1)-t(this.e/y))*y,n=r[n])for(;n%10==0;n/=10,e--);return 0>e&&(e=0),e},T.dividedBy=T.div=function(e,n){return M=3,C(this,new a(e,n),P,k)},T.dividedToIntegerBy=T.divToInt=function(e,n){return M=4,C(this,new a(e,n),0,1)},T.equals=T.eq=function(e,n){return M=5,0===i(this,new a(e,n))},T.floor=function(){return U(new a(this),this.e+1,3)},T.greaterThan=T.gt=function(e,n){return M=6,i(this,new a(e,n))>0},T.greaterThanOrEqualTo=T.gte=function(e,n){return M=7,1===(n=i(this,new a(e,n)))||0===n},T.isFinite=function(){return!!this.c},T.isInteger=T.isInt=function(){return!!this.c&&t(this.e/y)>this.c.length-2},T.isNaN=function(){return!this.s},T.isNegative=T.isNeg=function(){return this.s<0},T.isZero=function(){return!!this.c&&0==this.c[0]},T.lessThan=T.lt=function(e,n){return M=8,i(this,new a(e,n))<0},T.lessThanOrEqualTo=T.lte=function(e,n){return M=9,-1===(n=i(this,new a(e,n)))||0===n},T.minus=T.sub=function(e,n){var r,i,o,u,s=this,f=s.s;if(M=10,e=new a(e,n),n=e.s,!f||!n)return new a(0/0);if(f!=n)return e.s=-n,s.plus(e);var l=s.e/y,c=e.e/y,h=s.c,g=e.c;if(!l||!c){if(!h||!g)return h?(e.s=-n,e):new a(g?s:0/0);if(!h[0]||!g[0])return g[0]?(e.s=-n,e):new a(h[0]?s:3==k?-0:0)}if(l=t(l),c=t(c),h=h.slice(),f=l-c){for((u=0>f)?(f=-f,o=h):(c=l,o=g),o.reverse(),n=f;n--;o.push(0));o.reverse()}else for(i=(u=(f=h.length)<(n=g.length))?f:n,f=n=0;i>n;n++)if(h[n]!=g[n]){u=h[n]0)for(;n--;h[r++]=0);for(n=N-1;i>f;){if(h[--i]0?(s=u,r=l):(o=-o,r=f),r.reverse();o--;r.push(0));r.reverse()}for(o=f.length,n=l.length,0>o-n&&(r=l,l=f,f=r,n=o),o=0;n;)o=(f[--n]=f[n]+l[n]+o)/N|0,f[n]%=N;return o&&(f.unshift(o),++s),I(e,f,s)},T.precision=T.sd=function(e){var n,t,r=this,i=r.c;if(null!=e&&e!==!!e&&1!==e&&0!==e&&(j&&L(13,"argument"+w,e),e!=!!e&&(e=null)),!i)return null;if(t=i.length-1,n=t*y+1,t=i[t]){for(;t%10==0;t/=10,n--);for(t=i[0];t>=10;t/=10,n++);}return e&&r.e+1>n&&(n=r.e+1),n},T.round=function(e,n){var t=new a(this);return(null==e||H(e,0,E,15))&&U(t,~~e+this.e+1,null!=n&&H(n,0,8,15,v)?0|n:k),t},T.shift=function(e){var n=this;return H(e,-S,S,16,"argument")?n.times("1e"+c(e)):new a(n.c&&n.c[0]&&(-S>e||e>S)?n.s*(0>e?0:1/0):n)},T.squareRoot=T.sqrt=function(){var e,n,i,o,u,s=this,f=s.c,l=s.s,c=s.e,h=P+4,g=new a("0.5");if(1!==l||!f||!f[0])return new a(!l||0>l&&(!f||f[0])?0/0:f?s:1/0);if(l=Math.sqrt(+s),0==l||l==1/0?(n=r(f),(n.length+c)%2==0&&(n+="0"),l=Math.sqrt(n),c=t((c+1)/2)-(0>c||c%2),l==1/0?n="1e"+c:(n=l.toExponential(),n=n.slice(0,n.indexOf("e")+1)+c),i=new a(n)):i=new a(l+""),i.c[0])for(c=i.e,l=c+h,3>l&&(l=0);;)if(u=i,i=g.times(u.plus(C(s,u,h,1))),r(u.c).slice(0,l)===(n=r(i.c)).slice(0,l)){if(i.el&&(m=O,O=S,S=m,o=l,l=g,g=o),o=l+g,m=[];o--;m.push(0));for(w=N,v=A,o=g;--o>=0;){for(r=0,p=S[o]%v,d=S[o]/v|0,s=l,u=o+s;u>o;)c=O[--s]%v,h=O[s]/v|0,f=d*c+h*p,c=p*c+f%v*v+m[u]+r,r=(c/w|0)+(f/v|0)+d*h,m[u--]=c%w;m[u]=r}return r?++i:m.shift(),I(e,m,i)},T.toDigits=function(e,n){var t=new a(this);return e=null!=e&&H(e,1,E,18,"precision")?0|e:null,n=null!=n&&H(n,0,8,18,v)?0|n:k,e?U(t,e,n):t},T.toExponential=function(e,n){return _(this,null!=e&&H(e,0,E,19)?~~e+1:null,n,19)},T.toFixed=function(e,n){return _(this,null!=e&&H(e,0,E,20)?~~e+this.e+1:null,n,20)},T.toFormat=function(e,n){var t=_(this,null!=e&&H(e,0,E,21)?~~e+this.e+1:null,n,21);if(this.c){var r,i=t.split("."),o=+X.groupSize,u=+X.secondaryGroupSize,s=X.groupSeparator,f=i[0],l=i[1],c=this.s<0,a=c?f.slice(1):f,h=a.length;if(u&&(r=o,o=u,u=r,h-=r),o>0&&h>0){for(r=h%o||o,f=a.substr(0,r);h>r;r+=o)f+=s+a.substr(r,o);u>0&&(f+=s+a.slice(r)),c&&(f="-"+f)}t=l?f+X.decimalSeparator+((u=+X.fractionGroupSize)?l.replace(new RegExp("\\d{"+u+"}\\B","g"),"$&"+X.fractionGroupSeparator):l):f}return t},T.toFraction=function(e){var n,t,i,o,u,s,f,l,c,h=j,g=this,p=g.c,d=new a(q),m=t=new a(q),w=f=new a(q);if(null!=e&&(j=!1,s=new a(e),j=h,(!(h=s.isInt())||s.lt(q))&&(j&&L(22,"max denominator "+(h?"out of range":"not an integer"),e),e=!h&&s.c&&U(s,s.e+1,1).gte(q)?s:null)),!p)return g.toString();for(c=r(p),o=d.e=c.length-g.e-1,d.c[0]=R[(u=o%y)<0?y+u:u],e=!e||s.cmp(d)>0?o>0?d:m:s,u=z,z=1/0,s=new a(c),f.c[0]=0;l=C(s,d,0,1),i=t.plus(l.times(w)),1!=i.cmp(e);)t=w,w=i,m=f.plus(l.times(i=m)),f=i,d=s.minus(l.times(i=d)),s=i;return i=C(e.minus(t),w,0,1),f=f.plus(i.times(m)),t=t.plus(i.times(w)),f.s=m.s=g.s,o*=2,n=C(m,w,o,k).minus(g).abs().cmp(C(f,t,o,k).minus(g).abs())<1?[m.toString(),w.toString()]:[f.toString(),t.toString()],z=u,n},T.toNumber=function(){var e=this;return+e||(e.s?0*e.s:0/0)},T.toPower=T.pow=function(e){var n,t,r=m(0>e?-e:+e),i=this;if(!H(e,-S,S,23,"exponent")&&(!isFinite(e)||r>S&&(e/=0)||parseFloat(e)!=e&&!(e=0/0)))return new a(Math.pow(+i,e));for(n=J?d(J/y+2):0,t=new a(q);;){if(r%2){if(t=t.times(i),!t.c)break;n&&t.c.length>n&&(t.c.length=n)}if(r=m(r/2),!r)break;i=i.times(i),n&&i.c&&i.c.length>n&&(i.c.length=n)}return 0>e&&(t=q.div(t)),n?U(t,J,k):t},T.toPrecision=function(e,n){return _(this,null!=e&&H(e,1,E,24,"precision")?0|e:null,n,24)},T.toString=function(e){var n,t=this,i=t.s,o=t.e;return null===o?i?(n="Infinity",0>i&&(n="-"+n)):n="NaN":(n=r(t.c),n=null!=e&&H(e,2,64,25,"base")?D(l(n,o),0|e,10,i):B>=o||o>=$?f(n,o):l(n,o),0>i&&t.c[0]&&(n="-"+n)),n},T.truncated=T.trunc=function(){return U(new a(this),this.e+1,1)},T.valueOf=T.toJSON=function(){return this.toString()},null!=e&&a.config(e),a}function t(e){var n=0|e;return e>0||e===n?n:n-1}function r(e){for(var n,t,r=1,i=e.length,o=e[0]+"";i>r;){for(n=e[r++]+"",t=y-n.length;t--;n="0"+n);o+=n}for(i=o.length;48===o.charCodeAt(--i););return o.slice(0,i+1||1)}function i(e,n){var t,r,i=e.c,o=n.c,u=e.s,s=n.s,f=e.e,l=n.e;if(!u||!s)return null;if(t=i&&!i[0],r=o&&!o[0],t||r)return t?r?0:-s:u;if(u!=s)return u;if(t=0>u,r=f==l,!i||!o)return r?0:!i^t?1:-1;if(!r)return f>l^t?1:-1;for(s=(f=i.length)<(l=o.length)?f:l,u=0;s>u;u++)if(i[u]!=o[u])return i[u]>o[u]^t?1:-1;return f==l?0:f>l^t?1:-1}function o(e,n,t){return(e=c(e))>=n&&t>=e}function u(e){return"[object Array]"==Object.prototype.toString.call(e)}function s(e,n,t){for(var r,i,o=[0],u=0,s=e.length;s>u;){for(i=o.length;i--;o[i]*=n);for(o[r=0]+=O.indexOf(e.charAt(u++));rt-1&&(null==o[r+1]&&(o[r+1]=0),o[r+1]+=o[r]/t|0,o[r]%=t)}return o.reverse()}function f(e,n){return(e.length>1?e.charAt(0)+"."+e.slice(1):e)+(0>n?"e":"e+")+n}function l(e,n){var t,r;if(0>n){for(r="0.";++n;r+="0");e=r+e}else if(t=e.length,++n>t){for(r="0",n-=t;--n;r+="0");e+=r}else t>n&&(e=e.slice(0,n)+"."+e.slice(n));return e}function c(e){return e=parseFloat(e),0>e?d(e):m(e)}var a,h,g,p=/^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,d=Math.ceil,m=Math.floor,w=" not a boolean or binary digit",v="rounding mode",b="number type has more than 15 significant digits",O="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_",N=1e14,y=14,S=9007199254740991,R=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],A=1e7,E=1e9;if(a=n(),"function"==typeof define&&define.amd)define(function(){return a});else if("undefined"!=typeof module&&module.exports){if(module.exports=a,!h)try{h=require("crypto")}catch(D){}}else e.BigNumber=a}(this); +//# sourceMappingURL=doc/bignumber.js.map diff --git a/signer/rules/deps/bindata.go b/signer/rules/deps/bindata.go new file mode 100644 index 000000000000..0b27f451726d --- /dev/null +++ b/signer/rules/deps/bindata.go @@ -0,0 +1,235 @@ +// Code generated by go-bindata. +// sources: +// bignumber.js +// DO NOT EDIT! + +package deps + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _bignumberJs = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\xbc\x6b\x77\x9b\xc8\x93\x38\xfc\x7e\x3f\x85\xc4\xc6\x9c\x6e\x53\x20\x90\x9d\x38\x86\x14\x9c\x4c\x62\xe7\xe7\x79\x1c\x3b\x4f\x9c\xcc\xcc\xae\xa2\xc9\x91\x51\x23\x75\x82\x40\xe1\x62\xc7\x09\xfe\x7d\xf6\xff\xa9\x6e\x40\xf2\x25\xbb\xb3\x6f\x2c\xe8\x4b\x75\x75\x75\xdd\xbb\xf0\x68\x77\x70\x29\x17\x59\xbd\xba\x14\x85\xf3\xa5\x1c\x5c\x8d\x1d\xd7\xd9\x1b\x2c\xab\x6a\x5d\xfa\xa3\xd1\x42\x56\xcb\xfa\xd2\x89\xf3\xd5\xe8\xad\xfc\x2a\xde\xc6\xe9\x68\x7b\xf8\xe8\xf4\xe4\xd5\xd1\xd9\xab\xa3\xc1\xee\xe8\x3f\x46\xbb\x83\x55\x3e\x97\x89\x14\xf3\xc1\xe5\xcd\xe0\x87\x48\xe5\x62\x50\xe5\x83\x44\x7e\x7f\x0c\x5c\x91\x5f\x8a\xa2\xfa\x5a\xc8\x95\xc8\x46\x79\x55\xe5\xff\x59\x88\x45\x9d\xce\x0a\x5b\x7c\x5f\x17\xa2\x2c\x65\x9e\xd9\x32\x8b\xf3\xd5\x7a\x56\xc9\x4b\x99\xca\xea\x86\x96\x19\x26\x75\x16\x57\x32\xcf\x98\xe0\x3f\x8d\xba\x14\x83\xb2\x2a\x64\x5c\x19\x41\xd7\x31\x50\x5d\xfd\xdb\x8c\x09\xc8\xf8\xcf\xab\x59\x31\xa8\xa0\x00\x09\x39\xd4\x50\x42\x82\xd5\x52\x96\x81\x4c\xd8\x90\x25\x03\x99\x95\xd5\x2c\x8b\x45\x9e\x0c\x66\x9c\x17\xa2\xaa\x8b\x6c\xf0\xc5\x34\x4f\xd9\xf8\x19\x18\x71\x9e\x95\x55\x51\xc7\x55\x5e\x0c\xe2\x59\x9a\x0e\xae\x65\xb5\xcc\xeb\x6a\x90\x89\x6b\x03\x04\x87\x4c\x5c\xb7\xeb\x10\xc0\xac\x4e\xd3\x21\x66\xa6\xf9\x2f\x96\xc1\x18\x9e\xed\xc3\x5b\x30\x2e\x67\xa5\x30\x38\xff\x49\xfd\xe8\x36\x19\x94\x28\x2c\xc3\x00\xcf\x45\xcc\xba\x15\x13\x6c\x21\xdd\x41\x28\x12\x7e\xc9\xe1\x23\x4b\xe0\x9d\x95\x38\xc2\xf2\xe0\xab\x5a\x87\xe5\x68\xe8\xa3\x30\x10\xab\x9b\x35\x0d\x16\xdc\x34\xdd\x5d\x31\x44\xb7\x69\x86\x04\xec\xbd\x58\x1c\x7d\x5f\x33\xe3\x6f\x3b\x32\x2c\x56\xa1\x31\x31\xac\x73\xa7\x4c\x65\x2c\x98\x0b\x19\xb7\x8c\xa9\x65\x70\xcb\x60\x91\xff\xe9\x93\x63\x58\x95\x65\xf0\xe8\x89\x01\x7b\x07\x61\x16\x19\xd2\xf0\x0d\x83\x3b\x95\x28\x2b\x56\xf6\x84\x59\xb0\x04\x4a\xc8\x69\xbb\x79\xc4\x12\xa7\x44\x37\xf4\x46\x22\x62\x25\x96\x2d\x68\x8f\x83\xed\x71\xdf\x83\x2f\xa6\x59\x3a\x85\x58\xa7\xb3\x58\xb0\xd1\xdf\xee\x27\xc7\xdd\x6d\x3e\x39\x23\x20\xb8\xa9\xc8\x16\xd5\x32\xf4\x9e\x12\xa5\xdf\xc2\x25\xd1\x32\xc7\xa1\xc7\x7d\x02\xba\xff\x14\x11\x4b\x27\x5e\xce\x8a\x57\xf9\x5c\xbc\xac\x98\xcb\x1f\x5d\xa3\xc4\xd7\xac\x04\xcf\x85\x0c\x12\xa7\xe4\xb7\x22\x2d\x05\x11\xfa\x2e\x19\x7b\x22\x3b\x25\x0a\xa7\x84\xc4\x11\x28\x1c\x01\x89\x13\x23\xa3\xc7\x98\x47\xa2\x05\xcd\x7d\x01\x57\xb9\x9c\xb3\xb7\xe8\xfe\x6f\xb4\x46\x74\xd5\xb1\x6e\xd1\x41\xa0\x2d\x5a\xdc\x04\x22\xfe\xfb\xdf\xc4\x90\x79\xc1\x0a\x74\x41\xa2\x08\x64\x88\x9e\x1b\xc8\x11\x7a\x2e\x14\x96\xc5\x83\x1e\x35\x81\x85\x42\x68\x22\xa6\x1b\x04\x6e\x35\xaf\xf4\xfb\x1a\xae\xdb\x13\x51\xcd\xf7\x8f\x85\x07\xff\x17\xe2\xdd\xde\x12\x62\xac\xc0\xd2\x91\xd9\x5c\x7c\x3f\x4f\x98\xe1\x18\x9c\x87\xb6\x67\x9a\x6a\x7c\x77\x78\x86\x63\xd0\xa1\x71\x60\x92\xa0\x88\x59\x11\x2f\xd9\x48\x8c\x24\xe7\xa1\x1b\x31\x37\x2c\x4c\x93\x15\x28\x39\x14\x16\x5a\xdd\x3a\xd2\xf2\x38\xa8\x65\xeb\x4b\x92\xd4\x6c\xc1\x5c\x90\x9c\xfb\xdd\xf8\xb2\xe5\x02\x0e\x12\xdd\x60\xff\xf9\x7d\xb4\x25\x0f\x24\x91\x88\xd0\xac\xfb\xd1\x8f\x0c\xb4\xed\x9a\x07\xea\xb0\x36\xbb\x94\x50\x5b\x1e\xe7\x32\xd9\x9a\x0a\xb9\x69\x7e\x31\xcd\x7a\x8b\xed\x12\xa7\xdc\x15\x1c\x0a\x2c\x6c\x69\x7b\x50\x84\x3f\x38\x1d\x02\x1d\x07\x09\x73\x40\x84\x1f\xc8\x84\xbd\x09\x0b\xd5\x31\xa1\x1e\x77\x1a\x74\x07\xb2\x75\x6e\x53\x90\xc8\x0a\xcb\xe3\x3b\x37\xa0\xb7\x28\x2d\xbc\xe1\x50\x87\x52\xf3\x80\x34\xcd\xc4\x89\x9d\x75\x5d\x2e\x59\x4f\x25\x45\x12\xa8\x6d\xbc\x09\xea\x50\x06\xfc\xe1\x08\x09\x0a\x0e\x0f\xb6\x36\x47\x24\xbb\xb1\xbb\x7d\xdd\x6a\x2c\x6d\xac\x15\xad\x02\x69\xdb\x41\x69\xa1\xe1\x1a\xc4\x11\x3d\x3c\x2d\x1e\x83\xed\x6d\xbc\x45\xf7\xb6\xd7\x97\xaf\x49\x8f\x41\x05\x52\xeb\x4c\xd2\x96\x09\xc4\xb0\x84\x05\xac\x61\x8e\xe2\x0e\x9b\xc0\x0a\xdf\xc1\x35\x7e\x55\x2b\xee\x1d\x84\x95\x69\x2a\x51\xaa\xf2\xd3\xfc\x5a\x14\xaf\x66\xa5\x60\x9c\xc3\x3c\x44\xd7\x34\x59\x82\xbf\xc3\xef\xe8\x02\x8d\xb8\xc7\x55\xb0\x6e\x55\x5f\xc5\x61\x89\x6b\x67\x9d\x5f\x33\xd1\x6e\xcc\x9e\x73\xf8\x1d\x13\x58\x3b\x31\x96\x2c\x65\x05\x5b\x3a\x31\x87\xa5\x23\xb8\x12\x7a\x0e\x6b\x47\xe0\xda\x89\x7b\x4e\x5a\x60\xc9\x04\x54\xd4\x55\x63\x82\x8b\x8e\x69\x5c\xc4\xc5\xc4\xb6\x93\x69\xb0\x70\xd6\xf9\x9a\x71\xc5\x2e\xc3\xc5\xc4\x9d\xb6\x42\x64\xb8\x06\x35\xb9\xe1\x3c\xb2\xed\xda\xa7\x95\x70\x41\x4b\x61\x0d\x4b\xa7\x44\x09\x4b\x7c\xc5\x96\xb0\x86\x15\x5c\x13\xfc\x05\x2e\x9d\x18\x62\x5c\x3a\x05\xd4\xa8\x70\xca\xb1\xb6\x56\x96\x07\x73\x5c\x4c\xf2\x29\x24\x98\x8d\xc6\x10\x63\xdc\x34\x6e\x98\x37\x8d\x36\x0f\x8b\x49\x6e\x79\x53\x88\x71\x3f\xbc\x8e\x5a\x93\x31\x6f\x9a\x98\x9b\x26\x73\x11\xaf\x9b\xe6\x1a\x91\x2d\x9d\xf2\x85\x1b\xed\xf9\x63\xce\xfd\x79\x98\x34\xcd\x1c\x31\x31\x4d\xb6\xaf\x46\xc4\x4d\xf3\x0c\xf1\xda\x34\x3d\x73\x31\xc9\x6d\x6f\xba\x3d\xe9\xb9\x7f\xc0\x39\x78\xb4\xa2\xde\xa0\xc0\x38\x4a\x99\xe1\x19\x60\xaf\xb8\x4f\x1b\xed\xd8\xb7\xa3\x0f\xe6\x10\x73\x3a\x49\xdb\xce\x02\xcb\x22\x52\xe5\xd3\x30\x0b\x38\xed\x03\x5d\xc8\x9b\x86\x59\x56\x0d\x0b\xa7\xce\xca\xa5\x4c\x2a\xe6\x71\x2d\x98\x5b\x34\x1e\xb6\x14\xd6\x1d\x73\x75\xdc\x86\x11\x24\x21\xce\x03\x61\xe1\xb9\x12\xd9\x97\x15\x5b\x4c\xe6\x96\x35\xe5\x3c\x10\x98\x32\x01\x35\xbf\x6d\xd5\x98\xd8\xf0\xe2\xe7\x87\xbc\x58\x12\x2f\xd2\x11\x55\xa8\x89\x56\x91\x9d\xad\xc0\x85\xe7\x20\xe1\x8a\x47\x6e\x53\xf9\x5f\x61\x48\xea\xbc\x03\xe8\x54\xf9\x85\x56\x3d\xea\xbc\x73\xd2\xf5\x13\x77\x4a\x26\xd8\x11\x40\x60\xc8\x06\x2f\xb1\x60\x42\x31\x16\x7a\x87\x88\xb2\x69\xc6\xfb\x88\xd2\x34\x7f\x0b\xb1\x8c\x12\xb6\x84\x92\xfb\xa9\xfa\xe9\x15\x82\xc0\x8f\xac\x35\xd9\x9c\x30\x25\x7e\x23\x98\x3d\x2c\x62\x8c\x56\xed\xdc\x05\xca\xea\x10\xb3\xa6\xf9\x2d\xc4\x9a\x6b\xc5\x10\x64\x61\x1c\x2c\x95\xc0\x42\x4c\x1a\x6f\x89\xb4\x68\xdd\x0a\x2c\x39\x0e\x36\x96\xb0\xc4\x54\xb5\x92\x66\x0b\x63\x65\x79\x6c\x3b\x0b\x5d\x75\x70\x34\xdd\x31\x82\xcc\xb6\x5b\x48\x3c\xd8\xcc\xb6\xb0\xb6\x63\xe8\x86\xd6\x96\x87\x18\x9b\x66\x3b\x87\xdf\x99\xd4\x53\xae\x7c\xe1\x9a\x66\x1e\x19\xb6\x61\x2d\xfd\xe5\xe6\x64\xbe\xdf\xf3\xaa\xd0\xd5\x0a\x9a\x09\x62\x35\xad\x05\xe8\x09\xaa\xce\xa5\xa1\xb7\xc0\xb2\xe4\x8b\x4e\xac\x03\x85\x7b\xd1\xf7\xcb\x29\x87\x61\xe1\x94\xfc\x67\x85\x45\x70\x59\x88\xd9\xd7\xdb\xcc\x21\x7f\x8b\x55\x50\x10\xcc\x0a\x8b\x9e\x4b\xaa\x0d\x2e\xc7\x2d\x97\x14\xc4\x27\xba\x9b\x65\xa1\x68\x1a\x11\x56\x4d\x23\x86\x18\x33\xc1\x39\xe9\xfa\x02\x98\x6c\x1a\x63\x2e\x62\xb9\x9a\xa5\x03\xa5\x81\x4a\x83\x5b\xfd\xf0\xc8\x18\x90\x5f\x97\x27\x83\x62\x96\x2d\x84\xe1\x1b\x83\x2c\xaf\x06\xb3\x6c\x20\xb3\x4a\x2c\x44\x61\x70\xf2\x51\x86\x5b\xfa\xf2\x44\xaf\xae\xcf\x90\xe8\x51\xa0\x07\x12\xb3\x5e\x1e\xb2\x89\x6d\xcb\x69\x90\x75\x1a\x47\x19\x01\xcc\x26\xee\xf4\x57\x7e\x00\x6d\xd4\xaa\x76\x6f\x6c\x8f\x87\x3f\x22\xe1\xc4\xc4\x53\x8a\xdd\xfd\x37\x61\xa5\x1a\x26\x42\xa9\x6e\x9f\xd1\x6f\x05\xd4\x94\x71\xd8\x12\x9d\xd3\x0e\x2d\x8d\x12\x11\xf9\xa8\x28\xf2\x82\x4d\x0c\x7a\xfe\x4d\x2e\xce\xb4\x3b\x03\x46\xbc\x5a\x1b\xca\xc9\x4d\xe4\xc2\x00\x63\x2e\xaf\xf4\xdf\x0f\xf9\x49\x56\x19\x60\x88\x6f\x06\x18\x8b\x4a\xfd\x11\x06\x18\x69\xa5\xfe\xd0\xe3\x4a\x66\x75\x49\xbf\xf9\xdc\x00\x63\x9d\xaa\x97\x75\x21\x62\x49\xfe\xbb\x01\x46\x31\xcb\xe6\xf9\x8a\x1e\xf2\x3a\xa3\x31\x4a\x6f\x18\x60\x54\x72\x25\x68\x70\x95\xbf\x96\x0b\x59\xe9\xc7\xa3\xef\xeb\x3c\x13\x59\x25\x67\xa9\x7a\x3f\x96\xdf\xc5\x5c\x3f\xe5\xc5\x6a\x56\xe9\xc7\x62\xa6\xb6\x48\x2b\xe5\xd7\xaa\xe9\xdd\xd6\x8a\x9d\xac\x1b\x60\x6c\x36\x39\x9d\x88\xa9\x65\x30\x3e\x30\xac\xcc\x32\xfc\x81\x61\x55\x3c\xa8\x96\x45\x7e\x3d\x28\x9c\x6c\xb6\x12\xb8\x19\xac\xe9\x64\xc0\x5b\x74\xa1\xd8\x10\xf4\x63\xc7\x65\x9a\xa4\x7d\x1c\x01\x29\xc4\x30\x23\x95\x02\x4b\x7c\x4f\xfa\x65\xc6\x7f\x0a\x5f\xdb\x7a\x24\xe7\x74\x46\x47\x5d\xaa\xa3\x2e\xd5\x51\x2b\x7f\x46\x29\xa2\xcc\x96\xe0\x86\x39\xcf\x2d\xbc\x81\x1a\x33\x48\x70\x36\x49\xd1\x25\xc3\x90\x8c\x96\x13\x69\xd7\xb6\x37\xdd\xf1\xdc\xc6\xed\x75\x4e\x8a\x73\xc6\x72\xcb\xe3\xa3\x1b\x0e\x69\x88\xb3\xce\xec\x29\xd7\xb0\xe0\x4a\x72\x06\x42\x3b\x01\x5d\xe7\x0b\x4c\x83\x99\x76\x01\x5c\xe2\x41\x8c\x95\x2b\xea\x41\xbe\xa3\x56\xce\xed\x1b\xcb\xd3\x0e\xa6\xd6\xe7\x84\x76\x4a\xce\x8c\xf7\x10\xf5\xad\x39\x12\x62\x74\xc3\x3a\x72\xfd\x7b\xe8\xde\x2a\xd9\x2e\xc8\xe6\x65\x9d\xcd\x9b\x4d\x52\x8b\x8c\x14\xa3\x19\x89\x9f\xec\x74\x33\xc8\xf5\xda\x0f\xab\x88\xc5\x4d\x53\xb4\x16\xb0\x6a\x9a\x0a\x91\x89\x2d\x0b\x18\x87\x4f\x9b\xe6\xa9\xd6\x5a\xfb\x6a\x44\xa1\x2c\x20\x79\x1d\x79\xe8\x46\x75\xe8\x46\x2d\x1a\x53\xdf\xf5\x67\x93\x94\x60\xef\x78\xae\xe9\x6d\x03\xeb\x2c\x63\xd6\x34\xc3\xd9\xc6\xf4\x0f\x3a\x5a\xd1\xb9\x47\xa4\x6c\x85\x0a\xb6\x68\x08\x2e\x27\xd9\xce\xcd\x14\x48\xda\xec\xac\x69\x5c\xee\xab\x66\x25\x85\x20\x94\xcb\x80\x98\x47\xac\x87\x91\x42\x89\x1e\xa4\xb6\xcd\xfd\xad\x46\x8b\xf8\x61\x39\xb9\xb1\xf3\x29\x10\x7d\x91\x50\x5e\xb1\x0e\xe9\x9d\xe5\xa4\x9e\xf2\xdd\xd2\x77\x39\x14\x4a\x4b\x07\x5a\x4b\xba\x88\xa9\xd6\x30\x39\x7a\x50\x6b\x96\xaa\xd5\xb9\xd4\xea\x5c\xf2\x8d\x8b\x4c\x7d\x16\x96\xb4\xfe\x9d\x21\xa5\x3a\xba\x21\x96\xa4\x9d\x1d\x61\x59\x7a\x67\x78\x66\x9a\x4c\x3d\x91\x31\xd7\x6a\x97\x98\x78\x92\x2a\x28\xf4\x3b\xc4\x33\xcd\x55\x01\x91\xd4\x26\x57\xa0\x44\xef\x56\xa3\x33\xdb\x72\xae\x70\xa6\x5c\x06\xe2\x34\xad\xeb\x6e\x85\x23\xee\xab\x30\xe1\x88\x17\x6f\x14\x0e\xbd\x1a\xdb\xb2\xfd\x24\x5b\xaf\x94\xec\x7d\xc0\x99\xb3\x2e\xf2\x2a\xa7\x70\x0b\xbe\xb5\x76\xc2\xe3\xf0\x0e\xc7\x2e\x7c\xc5\x7d\xf8\x0d\xed\x03\x78\x82\x63\x0f\xde\xa0\xed\x89\x03\xf8\x81\xf4\xf7\x0b\x0e\x5d\xf8\x17\x1e\xc3\x1f\x38\xf4\xe0\x4f\xf4\xe0\x77\xf4\x5c\x17\xfe\xc2\x9f\xad\xe6\xbf\x10\xeb\x59\x31\xab\xf2\xc2\x27\xf7\x73\x51\xe4\xf5\x7a\xab\x09\xba\x26\xf9\x43\xf8\x7b\x50\x8a\x38\xcf\xe6\xb3\xe2\xe6\x4d\xdf\xe8\x42\xd2\x2a\xa1\x37\xf7\xe6\x0e\x8c\x7b\x5d\x6a\xf8\x6d\xd0\xb3\xd8\x2c\xcb\xab\xa5\x28\x30\x83\x99\xf3\xfe\xfc\xe3\xd9\xeb\xcf\x1f\xdf\xa1\xdb\xbf\xbc\x3e\xff\xf3\x0c\xbd\xfe\xf5\xd5\xd1\xc9\x29\x8e\xfb\xd7\xe3\xd3\xf3\xf3\xf7\xb8\xd7\xbf\xff\xeb\xe5\xe9\x31\xcd\xdf\xbf\xdb\xa2\x80\x3c\xbd\xdb\x76\xf4\xc7\xd1\x19\x3e\xbb\xdb\xa6\xa0\x1f\xdc\x6d\xd3\x4b\x3c\x87\x99\x73\xf4\xf1\xd5\xe9\xc9\x6b\x3c\x84\x99\xa3\x6d\x03\xf6\xa9\x17\xad\x02\x95\x3e\x24\x61\xc1\x9f\xb7\x20\x71\x56\x2c\xea\x95\xc8\x2a\xe2\x3c\x49\xee\x55\x42\xac\x66\xe4\x97\x5f\x44\x5c\x6d\xa2\xe6\x32\xda\x02\xd3\x92\xa5\x74\x96\xb3\xf2\xfc\x3a\x7b\x57\xe4\x6b\x51\x54\x37\x2c\xe3\x91\x56\x19\x4c\x60\x39\xc9\xa6\xdc\xa7\x60\x78\xe0\xde\xfa\x0f\x27\xcb\x2e\x8d\x50\x6d\xe6\xc8\x49\x45\xce\x65\x37\xab\x8f\xaf\x59\x86\xc6\xeb\xa3\x57\x27\x6f\x5f\x9e\x7e\x7e\x77\xfa\xf2\xd5\xd1\x85\xc1\xc9\x7f\x14\xe0\xc2\x11\x8c\x21\x23\xe5\xf3\x0e\xdd\x86\xa2\xc1\x49\x36\xc5\x77\xa0\xe6\x28\x02\x9d\x9c\xbd\xf9\xfc\xf6\xfc\xf5\xd1\x66\xca\xf3\x6e\xca\xd7\xad\x29\x5f\xf5\x94\xa3\xbf\xde\x9d\x9f\x1d\x9d\x7d\x38\x79\x79\xfa\xf9\xe5\x07\x9a\x43\xde\x11\x8f\xfe\xa5\x5c\x21\xb0\x8f\xc0\x6d\x67\x53\x8b\x37\xdd\xc6\xe0\x37\x02\x47\xa3\x9e\xa8\x07\x6f\xca\x7d\x5a\xd0\x3e\xda\x1e\x62\x33\xea\x65\x6e\x28\x22\x5b\xf8\x82\x73\xde\x22\x30\xf9\x0d\x9e\x4c\x5b\xbc\x5f\x9e\xbd\x39\x7a\x6c\x6d\xdb\xbb\xbb\xb8\xb7\x81\xfc\xa6\x5b\xfc\xc7\x2f\x17\x77\x1b\x11\xbd\x41\x9b\xfd\xb8\x8b\x80\xaf\x33\x66\x90\x59\xc6\x20\x9e\x65\xe4\x39\x5d\x8a\xc1\x0f\x51\xe4\x06\x88\x0d\x7a\x6f\xe0\x47\x8b\xde\xd1\xfb\xf7\xe7\xef\xd5\x11\x30\x81\x88\xc3\xa1\x68\x1a\x0f\x11\x45\xd3\x90\x36\x11\x11\x23\x45\xf0\x2f\x64\x5f\xa8\x8f\x47\xc7\x7e\xbe\xb5\xc8\x35\x01\xd5\x30\xbf\x68\x78\xaf\xde\xff\xd7\xbb\x0f\xe7\xff\x13\xbc\x3f\x70\xc8\xa8\x75\xb8\x6c\x9a\x8e\x35\x87\x1d\x6b\x2e\x39\x08\xd3\x1c\xfe\xa1\xf2\x03\xb4\x86\x11\x17\x37\xeb\x2a\x1f\xd4\xd9\xec\x6a\x26\xd3\xd9\x65\x2a\x0c\x58\xf2\xc7\x71\xf8\x43\xe3\xf0\xf6\xfc\xf5\xc7\xd3\xf3\x7b\x8c\x72\xd8\x51\xee\xcf\x2d\x46\xf9\x53\x4f\x78\x77\xfe\xe7\xe7\x77\xef\x8f\x5e\x9d\x5c\x9c\x9c\x9f\x3d\xc2\x8e\xbf\x6f\x4d\xf9\x5d\x4f\x39\x3e\x7f\xff\xb6\xe5\xa9\x07\xf2\x25\xa2\xbf\x50\x6c\x9f\x44\xeb\xc0\xb6\xe3\x36\xf8\xfe\x05\xc5\x2d\xcc\x9c\xd5\xec\x3b\x3e\x14\xaa\xef\x6c\x23\xce\x1f\x9c\xb4\xe2\x6a\xa8\xcc\xfe\xd7\xa1\x0b\x3d\x54\xfb\x7d\x0f\x34\x06\x1e\xba\xee\x81\x77\x78\x38\x7e\xba\x7f\xb0\xef\x1e\x1e\x8e\x21\xc3\xb7\xb3\x6a\xd9\x8e\x67\x7c\x57\x98\x63\xf7\xf0\xc0\x7b\xea\x3d\xa2\x26\x56\xec\xde\x58\xfe\x98\x3e\x78\xbe\xf7\xfc\xf9\x33\xf7\xf9\x2e\xf3\xdc\x83\xbd\x83\x7d\xef\xf9\x78\x7f\xf7\xce\xbc\xc6\xe5\x16\xeb\x46\xdd\xef\xd9\xe8\x8a\xad\x3c\xf3\xbd\xe4\x31\xba\x90\xe0\x64\x0a\x69\x6b\x93\xbe\x29\x6f\x4e\xb4\x01\xa9\xd8\x9c\xa0\xb7\x4f\xf1\xa8\xf0\xdf\x41\x8e\x73\x26\xc8\x61\xfb\x83\xcb\x84\x2d\x4d\x73\xe9\x2c\x44\xf5\x5e\xad\xfb\xc7\x2c\xad\x45\xa9\xcd\x7b\x85\x0f\x3a\x54\x80\xf9\x51\x66\xd5\xde\xf8\x65\x51\xcc\x6e\x58\xbe\x8b\x63\xce\x83\x3c\x2c\x03\x5e\xa3\xb7\xe7\xb9\x07\xe3\xdd\x6a\x52\x4e\x2d\x56\x4d\x4a\xcb\x9b\x86\x61\xe8\x79\x1c\xea\x10\x0f\x85\xf7\x34\x62\xc5\x3f\x00\x3a\xe6\x1c\x08\x06\x16\x24\xfa\x1a\x0e\x16\x4a\xfa\x59\xa2\x1d\xc7\x7a\xc7\x13\xde\x3e\x87\xd2\xc2\x31\x0f\x4a\xcc\x47\xe3\x3e\xb8\x54\x3b\xd2\x64\xfc\xed\xa6\xda\xde\xcd\x56\x23\x61\x7e\xd0\x23\x3e\x7e\xee\xed\x1f\xec\x1f\x1e\x3c\x3b\xf0\xdc\x67\x4f\x9f\xed\xb2\x3d\xcf\x24\x0c\xb8\xe5\xb9\x87\x87\x4f\x3d\xef\xd9\xf8\xe0\xe0\xe0\xd9\xae\xc6\xc5\xda\x1f\x1f\xee\x1f\x3e\x3b\x18\x1f\xea\x96\xf1\xd4\xf2\x9e\x1d\x1c\x1c\x8c\x3d\xfd\xbe\xd7\xee\x7e\x7f\xfa\xe2\x85\xf7\x8c\xeb\x97\xa7\xd3\x17\x2f\x9e\x73\x8b\x1e\x9f\x4d\x7b\x7a\xdc\xc5\xe9\x80\x3b\x71\xbe\xbe\x61\x15\x85\xf7\x8f\x6c\xf5\x40\x6f\xf5\x40\x6f\x55\xc9\x95\xb7\xff\x2b\xcd\xa0\xd2\x49\xa5\xf6\xdc\xda\x6d\x66\x8c\x03\x2d\x1b\xd6\xa6\xc9\x92\x49\x69\x59\x53\x6c\xc1\x07\xda\x83\x4a\x26\xb6\x5d\x4e\x41\x90\x57\x9d\x9b\xa6\x20\x6d\x8d\xef\x27\x37\xb6\x98\x42\x42\x47\xb2\x62\xf9\xa8\xe6\xbb\x35\x57\x3e\x16\x35\x05\x89\xf6\xb0\xa0\xb4\x6d\xae\x13\x56\x25\x4f\x70\x22\xfb\xac\xa4\x0e\x3f\x6c\xaf\x9d\xe2\xd2\x14\x9d\xb3\xe1\x20\x6d\xbc\xd1\x8b\x97\xca\x9b\x4c\xee\x7b\x93\xca\x55\xbc\x09\xc9\x53\xa4\xb1\x76\xd9\x3b\x68\xa9\x23\x50\x42\xea\xc4\x98\x40\x7a\x7b\xcb\x38\xbc\xda\x16\xf2\x3e\x5a\x12\x77\xc2\xcf\x3b\x82\xd3\xc5\xff\x24\x3e\x3b\x2f\x21\xc6\x6c\xf4\xb2\xd1\xe9\x03\x81\x7d\x02\x3e\x48\x6c\x3b\xe0\x39\x8a\x49\x32\xdd\x79\x09\xb5\x7a\xa0\x81\x50\x60\xbc\x9b\x5b\xf5\x6e\x0a\x12\xd3\xdd\xdc\x2a\x76\x5e\xee\xbe\xb4\xc8\xeb\x60\x72\x54\x29\xe1\x2e\x68\x20\xb7\xe2\xdd\x1a\x68\x1a\xca\x9d\xaa\x13\xeb\xd2\x34\x45\x9f\xbe\x2a\xef\x84\xcc\xd9\x83\x08\x4f\xe5\x99\x86\x58\xf0\x1c\xab\xb0\x88\x3c\xdf\xf6\x74\x18\xa6\xa9\x9b\xa3\x1b\x54\xa1\x54\xf9\x69\x52\x00\x13\x39\x1d\x62\x36\x91\x53\xfe\x93\x10\x97\xd3\x90\x5e\xf4\x34\xed\x58\xb7\x48\xe4\x9b\x45\x8b\xcd\xa2\x5d\x02\x41\x12\x58\xda\xbd\x98\x54\x53\x1b\x25\x48\xa4\xa7\x17\xd9\xa4\x22\x60\x2e\xd0\x1b\xca\xdd\xc2\x52\x03\xa8\x59\x07\x7b\x43\x32\xdb\xb4\xbf\xee\x5e\x25\x10\xdd\x99\xf3\xe0\xf6\xbe\x5e\xeb\x23\x58\xbd\xdd\x74\x93\xe4\x85\x6b\xb8\x82\x4b\x38\x87\x0b\x78\x0f\x2f\xe1\x08\x5e\xc3\x67\xf8\x0e\xc7\x28\x9d\x12\x31\x77\x4a\xb5\x25\x38\x41\xe9\xc4\x70\x8a\xb9\x13\xeb\x7b\xb4\x13\xd3\x3c\x51\x18\x9c\x9a\xe6\x29\x05\x56\x5d\x64\xa5\xd5\xa4\x74\x4a\xd3\xcc\xe9\x0f\x3b\x89\x86\xa7\x4d\x43\x83\x87\x48\x23\xfd\x53\x1e\x9d\x98\xa6\x8b\x48\x6d\x4d\x33\x3c\x8d\xdc\xdd\x63\xff\x78\xe4\xfa\xee\xc8\xd5\xbc\x7a\xd5\x6a\xdb\x63\x0e\x97\x78\xa5\x73\xed\x31\x4a\x47\xd8\xb9\x23\xe0\x18\x6b\x2b\xb6\x3c\x48\x9a\x86\x25\x78\x06\x31\x56\x4c\x3a\xa4\x72\xed\x8a\xe5\xea\x01\x8e\xf1\x78\x74\xd3\xb8\x1c\x96\xe8\x06\xa7\x93\xe5\x14\x91\x9d\x4c\x96\x53\x8a\xe7\x82\x65\x1b\x94\x53\x7b\xd8\x37\x9b\x66\x6c\xdb\xe0\x86\xc7\xfc\x52\x6b\x06\x8f\xc3\x02\x87\xee\x46\xc8\x8e\xf0\xa4\x63\xe8\xcf\x78\xda\x3d\x52\x10\x79\x6c\xe1\x18\xd6\x48\xe1\x1d\xa3\x4d\x5a\x1e\xe7\xb0\x0e\x3d\xd3\x64\xa7\x28\xd8\x29\xac\x21\xe1\x70\x82\x82\x9d\xe8\xc7\xad\xf9\x1b\xa8\x1c\x5e\xe2\x67\x38\xc7\x93\xfe\xaa\xe0\x33\x87\x0b\x3c\xef\xc2\xae\xcf\xe1\x45\x70\x3e\xb9\x20\xb5\xe2\xf2\xe0\x3b\x9e\x76\x12\x04\xdf\x7b\x3e\x77\x39\xbc\x56\x74\x86\xd3\x89\x37\x0d\x31\x19\x8d\x4d\xf3\xb5\x65\x05\xf3\x7c\xb0\x46\x97\x24\x91\x9d\xc2\x39\x7c\x86\x0b\x0e\x6e\x98\x46\xec\x3d\x9e\xd3\xf0\xcf\x43\xbc\x30\x4d\xf6\x1e\xdf\xef\x26\x16\x3b\x9f\x78\x8a\x28\x5c\xed\xea\xfd\xe8\xb5\xda\x4e\xc4\xd6\xa1\x4a\x4a\xaf\x31\xb1\x3d\x0e\xf3\xcd\xde\xae\x71\xde\x6d\x68\x83\xb1\x5a\x6d\x0e\xe7\x70\x4d\xab\x79\x88\x29\xcd\xb5\x6d\x28\xd8\x1c\xae\xc3\xcf\xd1\x77\xff\x14\xae\x21\xe1\x9c\xfb\x14\xf8\xae\x4d\x93\xa5\xb8\x46\x05\xba\xdf\xdd\x5d\xe0\xe1\xb5\x69\xce\xb7\xb7\x5b\xb0\x73\x98\xc3\x05\x21\x61\xb7\x4b\xdc\xc3\xa0\xdf\xaf\x17\x2a\x04\x2c\x4b\x4d\xba\x68\x11\xb8\x50\x08\x6c\xa1\xcd\x7d\xd2\xa4\xdd\xd0\x73\x54\xd9\xcd\xcb\xc9\x92\x08\xbf\x86\xd4\x34\x89\x60\x51\x7b\x12\x27\x93\x97\x44\x29\x9f\x9d\xe3\x84\x9e\xa7\x70\x81\x1e\x0f\xae\x97\x32\x15\x8c\xbd\xb4\xac\x17\x47\x5d\x52\xe4\x5c\x27\x4c\x8f\x49\x91\x2f\x70\xd3\x06\x97\x4a\x12\x2e\x3b\x09\xa6\xa0\x3c\x41\x3c\xd3\x7a\x62\x89\x1e\x1c\x23\x0d\x09\x8e\x95\xe2\x3e\x56\x8a\x5b\x31\xf1\x47\x76\x05\xb5\xc5\xae\x1c\x81\x4b\x2b\x56\x69\x44\xcb\x83\x12\x16\x6d\x26\x99\x3a\x62\xb8\x72\x0a\xb4\x16\x9d\x5a\xbc\x52\xba\xfc\x61\x88\x87\xa3\xbf\x99\x1d\x71\x97\x4d\xbe\x5f\xe6\x53\xce\x3e\x5d\x4f\x3e\x5d\x3b\xd3\xdd\x27\x7c\x24\x21\xa3\xde\xc9\xdf\xce\xd4\xe2\x9f\x9c\x27\x23\xa8\x70\xf4\xf7\x27\xa7\x6d\x79\x32\x82\x02\x47\x7f\xdb\x11\x3b\xc9\x12\x99\xc9\xea\xa6\x39\x9b\x9d\x51\xb3\xa4\x61\xe5\xee\x27\x8b\x29\x58\xbc\xf9\xfb\x53\x69\x35\x9f\x4a\xeb\xc9\x68\xf1\xc0\xfb\xba\xaf\xa3\xb0\x8c\x6a\xbf\xee\xaf\x8f\x24\x18\x4f\x3c\x43\x09\x6e\xa1\x2f\x45\x63\xce\x73\xa7\x44\x59\x9e\xcd\xce\x58\xac\xe3\x48\xdf\x0d\xe3\xc8\xf6\x7c\xaf\xbf\xf2\x18\x92\x16\x8a\x31\xee\x01\x09\xd8\x38\x7c\xda\x72\x75\x16\x0f\x8d\xef\x06\x22\xab\xb0\xba\x77\xad\x15\x79\xcf\x7c\xe3\x92\x3c\xef\x68\xec\x3f\x87\xc4\x34\x93\x21\xa6\x91\xf0\xb3\x5b\x4e\x6f\x2c\xc5\x04\xb6\xd7\xc8\x34\xb2\xfd\x7b\x05\x86\xeb\x50\x0b\x87\x7a\x88\xf1\x3d\x75\x19\x43\xca\x83\x2f\xfa\x8a\xd2\x50\x4e\xbc\x61\xb1\x24\x32\x06\x97\xb3\x52\x0c\x0c\x2b\xf1\x0d\x83\x93\x7f\xdf\xe6\x71\x6b\x0e\xb4\x71\xda\xef\x6d\xee\xc4\x98\xb7\x09\x17\x78\x8b\xae\x3a\xdd\x0f\xce\xec\xb2\xcc\xd3\xba\x12\xca\x07\x44\xf5\xfe\xf0\xc4\xdb\x7b\xb8\xa5\x2c\xef\xdf\x03\x30\xe1\x94\x24\x86\xe2\x16\x3e\x38\xb1\x90\xe9\x23\xd1\x40\x77\x1f\xa2\xe6\x03\xfd\x55\x49\xb4\x31\x57\x73\xf2\xd5\x7a\x56\x88\xf9\x87\x1c\x3f\x38\xf1\x6a\x8d\xdb\x34\xef\x41\xbc\x45\x0f\xa4\x02\xb0\x55\x58\xa1\xe6\xb7\xe9\x9b\x77\x2a\x6f\x8f\x1f\x9c\xf9\xfa\xb1\x9c\x44\xa1\x4a\x3b\x5a\xa3\x54\xf4\x44\xad\xd3\x54\xbb\xe9\x8c\x65\x58\x74\x77\x8b\x1e\xd9\x07\x8d\xe6\xe8\x86\xf3\xdd\x1b\xc8\x90\xc2\x23\xed\xc3\x65\x3b\x9e\x8b\xe8\x06\x99\x92\x2e\x41\x32\xda\x82\x73\x43\xa1\xa2\x4c\xb7\x25\xc7\x5c\x5e\xc9\xb9\x98\xff\x76\x83\xea\xf9\x57\x3b\xdb\x83\x57\xf7\x77\x06\xef\xe0\x2b\xdf\x02\xa1\xd2\xee\x62\x21\x8a\x0e\x96\x6a\xf8\x15\xc0\xfd\x47\x00\xba\xe0\x29\x80\xe2\x5b\x3d\x4b\x89\x4e\xe2\xdb\xaf\xa6\x3f\x05\xd2\x6a\x8f\x53\x3b\x49\xf3\xbc\xf8\xe7\x47\xbc\xa7\x26\x2d\x0a\x31\xab\x44\xf1\x61\x39\xcb\x90\xa2\xc1\x5f\x2d\xfc\xec\x91\x23\x0e\xdd\x7b\x10\xce\x8b\x23\xda\x82\x62\x97\x45\x25\x7e\x05\xeb\x80\xac\x08\xb2\xec\x91\x7d\x70\x1d\xf9\x67\x04\x58\x96\xc7\xa4\x87\xc4\xc3\x2d\x0d\x87\x9a\x63\xf4\xa8\x96\xfc\xd8\x3e\xff\x7a\xb8\x69\x6e\xb1\x4e\xa8\xdb\x3a\xbe\x1a\x6b\x58\x67\xb3\xb3\x47\xe6\xab\xa1\x65\x3b\x42\x2c\x66\x95\xbc\x12\xd8\xbe\x3c\x42\x70\x3d\xfc\x85\xab\x27\xfc\xb7\x28\xf2\xff\x09\x27\x17\x5b\xfe\x9f\xb8\x53\x9a\x91\x8a\xb2\x6c\x8f\x23\xfd\xe5\x71\x3c\x7f\xe4\x38\xf4\x82\xdd\xf4\xed\xb3\x48\x7f\x7d\x16\x87\xca\xde\xfe\xef\x87\xa1\x6e\x8e\xf0\x83\x53\xd6\x97\xf7\x40\xdd\x8d\x18\x14\x8c\x04\x4b\x47\xd5\x6a\xbd\x55\x62\x88\x5b\xbc\x9e\xa9\x5a\x9e\x61\xd2\x34\xc3\xec\xae\xfe\x54\x8e\x23\x19\xcd\xe1\xa6\xc0\x8a\x14\x98\x9d\x41\xe9\xac\xd3\xba\x64\x82\x07\xca\xaa\xa0\x3a\x41\x50\x39\xea\xd1\x0d\x2c\xb1\x74\x62\x58\xa0\x68\x55\x48\xda\x34\x43\x7d\xd1\x3a\x5c\x36\xcd\x70\xd1\x01\x5b\x46\xac\x85\x27\xb8\xaf\xd7\x5c\x44\xa5\xdf\xad\x3b\x5c\x6a\x57\x76\xab\xba\x60\x40\xcf\x0f\x67\xd1\xc0\xa8\xf4\xf7\x10\xbf\x46\xb6\xeb\xbb\xca\xd6\xa7\x58\xb1\x94\x2b\x3f\x56\xdd\x49\x2f\x7b\xbf\x2e\xc1\xd4\x8e\xb5\x1b\xc0\x6a\x74\xc3\x84\x47\x2c\x41\x3b\x81\x1c\x97\xdc\x67\x31\xa6\x90\xe3\x82\xac\x41\x21\xae\x44\x41\xb6\x0a\x32\x4c\xd4\x05\x6f\xbe\xb9\x03\xda\xea\xbe\xdd\x0a\x6a\x58\x8d\x2c\xe9\x6f\xad\xf9\x0b\x96\xf5\x77\xfb\x9c\x47\x89\x9f\x41\x82\x19\xba\x81\x0c\xb3\x20\xd3\x81\xcf\x72\x92\x4d\x87\xb8\x20\xad\xf9\xb3\x46\x7a\x7b\x41\x2f\x9b\xcb\x04\x0a\x7d\x73\x24\xaf\x78\x01\x0b\xcc\x41\x11\x40\x38\x25\xe1\xc5\xe4\x06\xbe\xad\x52\x15\x9d\xdf\xdb\xdd\x54\xeb\x9b\xe9\x49\xd1\xba\xb8\xd4\x94\xe1\x99\xed\x05\x32\x4c\xf4\xf5\xc8\x52\x5d\xb1\xbe\x58\xa8\xd0\x4b\x17\x5a\xc9\xa0\x30\xcd\x21\x75\x14\x53\x9a\x3c\xc5\x8c\x07\xb6\x4d\x4f\xb0\x9c\xc8\xa9\x85\x67\xb7\xf4\x6b\x23\xcd\x52\x77\x19\x14\x2a\xd3\x51\x04\xcb\x3e\x52\xb6\xed\xb8\xd7\xf8\xea\x94\x4e\x98\x80\x25\xc4\xdc\x57\x87\xa8\x4f\xcc\xf3\x3d\xd8\xba\xcc\x00\xa1\x14\xe1\x2a\x9f\xd7\x29\x09\xcb\x2a\x9f\x3f\xc2\xe1\xfa\xd6\x5c\xd5\x20\x6e\xcc\x9e\x77\x97\xb7\x87\xd2\x89\x9b\x66\x28\x9c\xb2\x69\x04\x89\xf6\x50\x17\x2e\x44\x1b\x06\xf7\xa9\xa9\x69\xa4\xea\x95\xdb\xbd\x92\xfb\xec\x10\xf1\xcf\x88\x15\x4a\x44\x94\xed\x86\x0a\x5f\x31\x09\x02\x5c\xd8\xe3\xaa\xa9\x80\xca\x29\x77\xb1\xe0\xfe\xa6\xeb\x4f\x0e\x52\x0b\x28\xab\x1c\x75\x51\xcb\x04\xd7\x36\x21\x23\x6d\x25\xe6\xa8\x9e\xfe\xa9\xef\xa0\xce\x5a\xfb\xbb\xda\x58\x92\xf4\x91\xfb\x31\x7f\x8c\x32\x1d\x5d\x20\xa7\x78\xb3\x95\xfa\xf1\xa3\x52\x9f\xff\x5a\xea\xf3\x87\x52\xdf\xed\xa9\x15\xfb\x1a\x55\x7c\xa8\xab\x40\x46\x37\x90\xa8\x70\x36\xed\xc5\xbe\x6e\x9a\x61\xa9\xc5\x9e\xb4\x4b\x7a\x77\x9d\xbc\x93\xf2\x44\x4b\x79\xba\x25\xe5\xf4\x4c\x6e\xa0\x1a\x48\xfd\x91\xf4\xdd\xdd\x5c\x89\x75\x8d\x15\xab\x39\x29\x36\x56\x92\x28\x27\xbd\x58\xe7\x58\xdb\x6d\xde\x2c\x0f\xdd\x88\x95\x58\x43\x81\x29\xf7\x59\x8e\x76\x0e\x05\x26\x1c\x8a\x8d\xcc\x06\xb9\x6d\x07\xc5\x46\x9c\xb7\xba\xda\x9b\xb9\xa4\x0b\x77\x32\x4c\xbb\x47\x37\xcc\xed\x4c\xd5\xdd\xa5\x40\xee\x69\x82\x05\x64\x98\xd3\xea\x6e\x90\x05\x3c\x47\x96\x4c\x6c\x3b\x9b\x62\x32\xc9\xa6\x56\x4a\x7f\x72\x3e\x3a\x6b\x5c\xa0\x86\x1d\x3c\xeb\xce\x35\x37\x4d\x96\xf4\x21\x57\xce\xc1\xb2\x4a\x0e\x24\x1f\x09\x94\x8a\x57\xfa\x3a\x00\x52\xf3\xdb\x27\xad\xcf\x59\x65\x3d\xf4\x49\x4b\x2c\x34\xd1\xfb\x0c\xaa\x18\xaa\xf4\xbd\x69\x7a\x43\xa4\x77\x57\xff\x30\x9d\x7f\xdb\x03\xa3\xcb\x39\x1b\x2a\x05\x0f\x62\xa8\x87\xb7\x59\x58\x4e\xc2\x73\xdf\xf3\xab\x50\xf6\x5e\x1f\x64\x58\xed\xde\x58\x24\x10\x72\x52\xb5\x5a\x23\xa8\x5a\x77\xaf\x52\xee\x5e\x46\xee\x9e\x4e\x63\x4a\x52\x0b\x95\x0a\xb4\xda\x3e\x0a\xb4\xfa\x5b\x4b\xd3\x2c\xc8\x05\x0a\x89\xb2\xe4\x5b\x0a\xcb\xe3\xa0\xcc\x9c\x2a\x7b\x78\x4c\xfc\x1f\x11\x15\xa6\x2b\x91\x44\xd3\xf4\xf9\xe3\xa7\x9c\x9b\xe6\x47\x56\xc1\xbf\xff\x2d\xac\xde\xd3\xba\x53\x60\xec\xc2\x73\xf0\x9e\xea\xca\xa7\xcc\xff\xca\xa1\xa2\x75\xd5\xa9\x3c\x24\xf9\x1d\x85\xa3\x6e\x75\x2e\xe0\x02\xbc\x67\x5b\xf4\xe4\x51\xd6\xca\xbc\xe1\x09\xc3\x52\xb5\x33\x2d\x2b\x67\xa4\x65\x32\xa5\x64\x4c\x93\xd9\x17\xba\x68\xe6\x82\x66\x94\xbb\xea\x1e\xc8\xf5\x3d\x52\x4a\x99\x3a\xff\xf2\x5b\x3d\x2b\xc4\xfb\x3c\xaf\x88\x01\xbe\x15\xd5\x63\xce\xfa\x03\x3b\x4f\x22\x58\x3a\x25\x45\x7a\xaa\x90\xea\x9d\xb5\x0f\x8b\x96\x5a\x86\xeb\x3c\xd5\xc1\x1e\xb1\x05\xd9\x65\x92\xcc\x64\x4b\xf4\xf4\x38\x32\xd9\xae\x0a\xeb\x69\x80\xea\x8f\xdc\x91\xeb\x27\x51\xa9\x10\x0c\x94\x7d\x55\xa9\x7f\xc2\x8b\x11\xe7\xba\x0a\x60\x8a\xe8\x8d\xdc\x88\x4e\x91\x25\x1c\x58\x57\xc6\x63\xc5\x7c\x67\x8c\xaa\x8a\x31\xd3\x35\x52\xb0\x0d\x20\xd3\x86\x9a\xc5\x96\xc7\x47\x63\x6e\x33\x37\x8c\x9b\x26\xde\x19\xd3\x30\x05\x31\x43\x4d\x4e\x9f\x91\x34\xde\x29\x75\x51\xe6\x39\xdb\xd4\x64\x6f\x2a\x2c\x85\xc1\x2d\x8f\x5b\x31\x07\xd9\x52\x20\xe3\xdc\xef\x9e\x53\xcb\x30\x48\x53\xd3\x79\x28\x43\xa9\xb2\x61\x90\x62\x6c\x2d\x61\x4f\x6d\x3f\x25\x83\x19\xe8\xfa\x57\x09\x64\x69\xf5\xd1\xd6\xda\x01\x7a\xc5\x4a\xa8\x61\x09\x9e\xba\x9c\x63\xb5\x13\xf3\x1e\x8d\x94\x6b\x37\xae\x60\xd2\x89\xf9\x76\xbb\xd2\x89\xd2\x11\x2f\x62\xd3\xb4\xed\x74\x0b\xf9\xd4\xde\x83\x94\x78\xdf\x38\x3c\x3c\x3c\x34\x14\x8f\xb2\xbc\x69\x8c\xfd\xf6\x95\xf3\x9f\x6c\x68\x65\x4d\x33\xb4\xb2\xbe\x10\xd9\x34\x8d\xa7\x06\x62\xd6\x55\x06\xba\xc4\xf4\xec\x23\x93\x20\x1d\x61\xbd\xb3\xc6\x40\x31\x27\x0e\x65\x8b\xbc\xe4\x8e\xf8\xc6\xca\xed\x6a\x85\x61\xae\x66\xd4\x50\xb7\x33\x5c\x0e\x75\xb7\xd7\x6e\x38\xff\x29\xb1\x6e\xe7\x2c\x2d\xdc\x87\x94\xfe\xe4\xe8\xdd\xf6\x81\x4d\xb7\xa4\x07\x5f\x5b\x33\xae\x60\x90\x15\xaf\xd3\xff\xc9\x4f\x6d\xeb\x80\xba\x04\xea\x4a\xa7\x50\x35\x57\x9f\xe3\xa5\x13\xc3\x05\x92\x1d\x3b\xb8\x63\xc7\x78\x97\x39\x3d\x37\xcd\x0b\x9d\x41\x32\xcd\x8b\xad\xcc\xe9\xf0\x92\x0c\xa7\xf6\x00\xce\x4d\x73\xa8\x47\x0c\x2f\x9a\xe6\x82\x7e\xf4\xdb\x79\x5f\x5f\x21\xda\xf8\x5f\x79\x27\xbb\x78\xe9\x94\x40\x90\x23\x5d\x6b\xe1\xea\xfa\x15\x97\xfb\xdb\xf5\x18\x1c\x44\x5b\x92\x56\xb1\x4b\x15\xc9\x58\x15\x13\x3a\x61\xda\x43\x49\x37\xb9\xb3\x05\x5e\xf4\x8f\x8a\xc7\x56\x78\x0e\xe7\x78\x01\x17\xb8\x82\x5c\x99\x15\xe5\xe4\x91\x49\x49\xad\x05\xac\x70\x32\x55\xb6\x6a\xb5\x55\x7e\x94\x17\xec\x1a\xcf\xe0\x0a\x5f\x92\xab\x1a\xd8\x76\x1e\xa2\x1b\x6c\x8a\xe4\xd7\x78\x31\xc9\xa7\x3b\x57\x30\x57\x0f\xa3\xab\xc6\x85\x12\x53\xa8\x31\xb7\xca\xa0\x0e\xf3\x80\xc7\x78\xae\xee\x4d\x76\xae\x60\x89\xe7\x93\x52\x0f\x4a\x70\xbe\x1b\x5b\xcb\xdd\x35\xc4\xb8\xde\x8d\xad\x64\xe7\x6a\xf7\xca\x5a\x4d\xea\xa9\x55\x40\x81\x2c\x1e\x5d\xab\x1b\x82\x84\x46\x73\x6b\xbe\xbb\x84\xd5\xa4\xb6\xed\x29\xc6\x3b\xd7\x01\x8d\xc3\xa2\x63\x87\x22\xb2\x2c\xe9\xaf\x7a\x67\x90\x6c\xdb\x0a\xa4\x66\x8b\xb6\x6c\xed\x1f\xaa\xf6\xc1\xbd\xcb\x41\x8f\x94\xfb\xf3\xed\x52\x39\x7d\x51\xa8\x5c\xa4\x0c\x1f\x2a\xf8\xe7\xbd\x82\x07\x11\x91\x41\xa0\xe5\xfc\x4a\xa3\xb2\xa5\x4b\x1e\x0f\xcb\x3e\xb7\xa1\xd8\x83\xfb\xc9\x43\x1e\x91\x65\xf1\xda\x85\xa9\x41\x83\x54\x95\x77\xff\x37\x60\x63\x57\x03\xeb\xcc\x54\x07\x73\xec\x76\x30\x55\x0d\xdf\xa3\x14\xfb\x25\x4c\xef\x17\x30\x3d\xa5\xc3\x75\x9c\xbb\xe5\x36\x3a\xe5\x3a\x95\x95\x2e\x4d\xcf\xd1\xfa\xcb\xe9\x0b\x79\xa0\xa6\xd7\x87\xb5\x3c\x50\x62\x37\xaa\xab\xe2\x21\x4f\x90\x84\x25\x45\x39\x51\x25\xda\x5d\xfc\x0d\x33\x8c\xa3\xa4\xd7\x5b\x7e\x02\xcb\x4d\xf9\x53\x1b\xe6\x14\x98\x93\x27\x07\x35\x16\xb0\xb4\xb1\xe0\x90\x87\xae\x69\x2e\x43\xb7\xe3\xee\xe5\x4e\xde\x34\x39\x24\x38\x6b\xbf\x89\x60\x2e\x14\x3c\x58\x86\x45\x50\x58\x98\xf3\xc4\xc2\xd2\xea\xfb\x0a\xc8\x79\x50\x87\xaa\x7c\xbe\xed\x50\xcb\x17\x9c\x43\xac\x6a\xea\x0d\xdb\xb0\x12\x7e\x5b\x61\x1a\x25\xd6\x5f\xce\xfd\x12\x27\x8b\x82\x44\xeb\x2f\xe7\x41\x59\x12\x8f\xd2\x4d\x66\x72\xeb\x4b\xa1\x4f\x9f\xe6\x3f\x0d\xab\xb6\x8c\xdb\x4f\x9f\x7e\x33\xc0\x58\x18\x1c\x8c\x27\xa6\xf1\x00\x46\xb7\x02\xf7\x53\xee\x27\x9b\xc2\x5c\x7d\xd8\xed\xd0\x47\xdd\xbe\x7b\x4a\x13\xbf\xc0\x42\xab\xca\x35\x2e\x9c\x18\xe6\xfd\xbd\x3a\xac\xb0\xda\xbc\x5c\x63\x72\xe7\xc6\xbd\x67\x17\xf6\x05\x87\x1e\x94\xd8\x97\x62\x7f\xc1\x25\xb0\x21\xa3\x48\x5e\xe5\x70\x18\xe7\x4d\x53\x3a\x69\xc5\xbe\x29\xe3\xa2\xcb\x23\xc6\x60\xac\x66\xdf\x07\x73\x91\xe5\x2b\x99\xd1\x56\x06\x86\xc5\x96\x91\x71\xaf\x06\xf8\xb1\x12\x60\x81\xc3\xa5\x69\xaa\x84\xcb\x47\x56\x82\x76\xcc\x3c\xee\x2c\x2a\xc1\xbe\xf1\xa8\xf4\x3b\x37\x74\xdd\xc7\xfe\xdb\x65\xe8\xda\x5c\x17\x6c\x4d\x7c\x3a\x77\x04\xf6\x89\xa3\x85\x23\x6c\x0f\xe6\xca\xaa\xe3\xfb\x09\xab\x31\xdf\xb9\xe1\x2f\xdc\xe8\xc6\xaa\xfd\x7a\x4a\x0b\x0b\xda\x4b\xbc\x5a\xb3\x39\x0f\xdd\x88\x82\x85\xb9\xbf\xf2\x4b\xa8\xf1\x07\xfc\x20\x6f\xa3\x27\x45\xcc\x21\xd1\x90\xdc\x20\x45\x32\xf7\x73\x95\x1d\x54\xb2\xa2\x5c\x80\xb4\xb5\x92\xd7\x9c\x83\x37\xa4\x10\x68\xb5\xa6\x08\x89\x57\x78\x0d\xd7\x28\x61\x85\xc9\xdd\x91\x12\x57\x9c\x22\x17\x09\x73\x2c\xdb\x90\x6a\xd3\x37\xe7\x14\xdc\xc8\x4e\xef\x49\x7c\xc5\x44\x17\x4b\x72\xb8\xd6\xab\x27\x1d\xcc\xce\xa4\x13\xc4\xaa\x43\x49\x6e\xa1\x94\x38\x25\xae\x9c\x12\x17\x4e\x09\xf9\x2e\x8e\x21\xc3\x57\x8c\xac\x6b\x0e\x5f\x79\x0b\x77\xc1\x9d\xd9\x65\xc9\xb8\x42\xfd\x15\x4b\xa0\x7a\xac\x97\xbf\xf0\xa2\xc9\x6a\xeb\x0c\xe0\x7a\xeb\x65\xea\x4f\x92\xed\xbe\x6a\xbb\x0f\x7e\x60\xad\xdd\xf9\x2a\xd7\x35\xc2\x0f\x23\xdf\x2d\xc7\xda\x12\x4d\x43\x06\x38\x72\x77\x85\xa3\xf3\x41\x7a\xee\xbb\xfc\x5a\xa5\x15\xd7\xf9\xf5\x2f\xa2\xa1\x55\x57\x4d\x65\x09\xde\xa5\x07\xc8\x41\xe8\x5d\xf5\xf1\x1e\x18\xa2\x55\xf7\xaa\xfe\x67\xd8\x65\x35\x99\xe0\x4d\x53\x84\x17\x14\x03\x8d\xd0\xe5\x4d\xb3\x9e\x15\xa5\x38\x4e\xf3\x59\xc5\x04\x57\x72\x32\x64\x02\x09\x9d\x7b\x37\x0d\xca\x8f\x5d\xe7\xd7\xcc\x92\x20\x78\x97\x61\xf9\x3d\x9a\xb3\xdf\x47\x37\xd6\x98\xfb\x2e\x6c\xa4\xb0\xad\x48\x2d\x76\xc6\xea\x57\x5d\x8b\xb4\x6e\x19\x0c\x2b\x27\x6e\x2b\x45\x33\xd3\xac\xfa\x6c\xa8\x0a\x8c\x36\xaf\x98\x71\x5d\x1e\xbc\x62\xc5\x68\xcc\xa1\x2b\x5a\x0e\x24\x6e\x7c\x3c\xc8\x4c\x53\xa5\x35\xe4\x5d\x30\xf2\x0e\x98\x3b\xd9\xf8\x0a\xbf\x39\x73\x79\xc5\x2a\xce\x21\x53\x56\xf2\x77\xf8\xda\x5b\xc9\xbe\x48\xfc\x9f\x9b\x35\x55\x15\xb7\xff\x2b\x33\x0d\xe3\xfd\xf6\x60\x35\xa7\x3c\x76\xa6\x5d\x7c\x5b\x11\xff\x62\xe5\x88\x60\x2b\x28\x45\xc4\x3c\x92\x14\x6c\x18\xdd\x1d\x99\x01\x6e\x28\x55\x14\x49\x6a\x9d\xbc\xfd\x0c\x8d\xb3\xd9\x99\xe1\x2b\x57\x9c\xe8\xdb\xfb\x07\x2d\x92\xea\x0b\xd3\xf1\xd3\xee\x13\xd3\xe8\x35\x4b\x59\x06\x39\x07\xb7\x11\xe0\xb9\x20\xb9\xff\x5b\x88\x64\x73\x42\x7c\x12\x25\xaa\xcf\xef\x86\xd0\x62\x55\x17\xd1\xf5\x8b\xb6\xcc\x5e\xd4\x59\xdc\x66\x7b\xd4\xf3\x3f\xbf\x0b\xd0\xf7\x0f\x57\xb3\xb4\x16\xe7\x09\x4d\xcf\x7f\xbf\x38\x7f\x24\x13\xae\x53\xdb\x1b\x51\xbb\xdd\xd0\xbf\xab\x3a\x25\x75\x3e\xdb\xd4\x4b\x54\x9b\x58\xd6\x6d\x7a\x6a\x8a\xd0\x6d\x1a\x81\x88\x59\x94\xf9\x99\xed\xdd\xa9\xaf\xd8\x54\x56\x68\x21\xf3\x40\x6e\x8a\x50\x72\xf5\x9d\x8a\x65\x18\x81\x0c\x8b\xd6\x03\xcd\x50\xa8\x6c\xa3\x65\x18\x50\xe1\x8d\xdd\x7f\xcb\x51\xd9\x76\x90\x51\xf4\x67\x65\x3c\xc8\x2d\xcc\x6e\xdb\x42\x90\x3b\x5f\x25\xe6\x77\xbf\x4a\x94\x3c\xe8\xdd\xc0\x7c\xf3\xbd\x9f\xe5\x35\x8d\xc7\x37\x88\xca\xfb\xb9\x41\xe1\xc4\x90\x53\x54\xa4\xbe\x29\x2a\x49\xa7\x3b\xa5\xaa\x9f\xa1\x18\x2f\x73\xc4\x56\x96\xea\x61\xa6\xc3\x34\x87\xca\x89\x29\x30\x37\xcd\x61\xae\x8a\xba\x9a\xa6\xbf\x0d\xab\xa2\x22\x72\x7d\xbb\xf4\x6b\xe5\xb8\x0c\xb1\x87\x51\x6b\x00\x6e\x58\x43\x81\x09\x62\x0a\x43\xd9\x34\xc3\x9c\xf7\x5e\xb1\xeb\x0f\xe5\xdf\x95\x2e\x6b\xb9\x73\xc5\x96\x84\x69\xd7\xae\x8b\x8b\x58\xd2\xa7\x5c\xf8\x0b\x96\xf6\x74\xe2\x51\xe2\x93\x33\xef\x06\x65\x58\x07\xb5\xce\x22\xcb\x49\x3d\x1d\x62\x3e\xa9\xfb\x60\x9e\x5a\x42\x6a\xe8\xa0\xf6\x9f\x49\x63\x1a\xb9\xfe\x66\xb9\x0d\x15\xf3\xbb\xb7\xb7\x4c\xe8\x8f\x7f\x42\x72\xa6\xab\x10\xb7\xaa\x7d\x6a\x62\x8c\xf6\xa3\xbf\x89\x2e\x8e\x1c\xa8\x52\xb8\xa9\x81\x78\xae\xde\x37\xe5\xe7\x3d\x8b\xea\xef\x91\xc4\xd6\xb9\x95\x0f\xbe\xff\x21\xf7\x46\x45\x5b\xb5\x2a\x94\xef\xbf\x77\xa2\xbd\xb6\xdf\x80\x6e\x38\x46\xda\x76\x90\x4f\xe4\x74\x17\xb3\xb6\x1e\x6c\x52\xa0\x3b\xb5\xf0\xbc\x4f\x03\x88\x2e\x30\x26\x42\xf1\xa0\x78\xd1\x4f\x2e\x2c\x8b\xe7\x93\x62\x1a\x56\xea\x6b\x5d\xad\x53\xf2\x49\x61\x79\x24\xce\xfa\x01\x5d\x0e\xfa\xc9\xa2\xae\xe9\xa8\x6a\x5c\x6a\x98\xee\x60\xd5\xeb\xcf\xed\xbb\x80\x7e\x67\xc9\xb6\x7e\x64\x9b\xaa\xa2\x48\x6c\x22\x75\xcb\x70\x0c\x4b\x6c\x5c\x62\xc1\x2d\xe6\x86\x59\x64\x90\xdf\x24\x2c\x83\x5b\xd9\x06\x60\x7a\x87\xc5\x75\xd9\x5a\xd6\xb9\xc5\x86\xeb\x18\x81\x65\x65\xe4\x04\xab\x6f\xd0\x04\x16\x96\xe8\x0b\x0c\xab\x8d\xc8\x5a\x56\x16\x56\x9b\x69\x06\x64\x36\x56\x81\x6d\x6f\x4d\xb5\xb0\xd0\x33\x2b\x65\x33\x36\x75\x65\xfa\x93\xf7\x2d\x9c\x33\xbe\x89\xd1\x36\x98\xc6\x1b\xe6\x18\x08\xbc\x63\x48\x81\x2c\xf4\x9c\x09\xee\xaf\x88\x0f\x68\x33\x33\x1d\xf7\xeb\x6a\x87\x4f\x73\x8b\x7d\x72\x3e\xcd\x77\x79\xd4\xd0\xaf\xc5\x99\x98\x58\xf6\x34\xa2\xc7\xe8\xc9\x88\xdc\x26\x65\x70\x63\x21\x53\x58\xe9\x67\x75\xd5\x0a\xd7\xd8\x56\xeb\x0e\x2e\xf3\x3c\x15\xb3\x6c\x90\x17\x83\x4b\x99\xcd\x8a\x9b\xc1\x9c\xc2\x4d\x03\xae\x50\x7f\x49\x25\xb3\xc5\x60\x95\xcf\x85\x01\x97\xdd\x87\xe9\x03\x62\xd4\xc1\x72\x56\x0e\x56\x79\x21\x06\xd5\x72\x96\x0d\xbc\xa7\x83\x52\x2e\x32\x99\xc8\x78\x96\x55\x1a\x48\x69\xc0\x39\x1a\xae\x37\xde\xdb\x7f\xfa\xec\xe0\xf9\xe1\xec\x32\x9e\x8b\x64\xb1\x94\x5f\xbe\xa6\xab\x2c\x5f\x7f\x2b\xca\xaa\xbe\xba\xfe\x7e\xf3\xe3\xe5\x6f\xaf\x5e\x1f\x1d\xbf\xf9\xd7\xc9\xef\xff\xdf\xe9\xdb\xb3\xf3\x77\xff\xff\xfb\x8b\x0f\x1f\xff\xf8\xf3\xaf\xff\xfa\xef\x27\x9f\x0d\x38\x43\x4f\x78\xfb\x70\x83\xde\x3e\x5c\xdc\x2f\xec\xf5\xe0\x3d\x4e\x3c\x32\x3f\x9e\xeb\x82\x27\xf6\xc0\x13\xfb\xe0\x89\xa7\xe0\x89\x67\xe0\x89\x03\xf0\xc4\x73\xf0\xc4\x21\x78\x82\x06\x09\xcf\xa3\x3f\x63\xfa\xb3\x37\x85\x97\xea\x43\x8e\x23\xf4\xc4\xa1\xfa\xa2\x4a\x55\x51\x1a\xdd\xf1\x6c\x8a\x9d\xe7\x22\x91\x99\x30\x4d\xfd\xeb\xcc\x56\x73\xae\x1f\xd9\x43\x53\x33\xbb\xdd\x7c\xb7\x69\xd4\x99\x1e\x37\xdf\x54\x7f\xab\x0b\x1b\x61\x9a\xfa\xd7\x21\x2f\xab\xa8\xf4\x05\xc0\xdd\x26\x9c\xc1\x70\xc9\xab\xe2\xe6\xe7\x12\x0b\xf1\xad\x96\x85\x60\x6d\x3d\xa8\xc1\x6f\xe3\x59\x15\x2f\xd9\x6b\xfe\xf3\x56\x73\xa0\x70\xfa\x2f\xcb\x70\x76\xdb\x66\x05\xfe\x63\x34\xfa\xcf\x41\x99\xd7\x45\x2c\xde\xce\xd6\x6b\x99\x2d\x3e\xbe\x3f\xc5\x79\x1e\xdf\xf9\xf7\x1a\xce\x6a\xb6\xfe\x8f\xff\x17\x00\x00\xff\xff\x2f\x88\x72\xca\xa2\x43\x00\x00") + +func bignumberJsBytes() ([]byte, error) { + return bindataRead( + _bignumberJs, + "bignumber.js", + ) +} + +func bignumberJs() (*asset, error) { + bytes, err := bignumberJsBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "bignumber.js", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "bignumber.js": bignumberJs, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "bignumber.js": {bignumberJs, map[string]*bintree{}}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/signer/rules/deps/deps.go b/signer/rules/deps/deps.go new file mode 100644 index 000000000000..5ee00b900ae8 --- /dev/null +++ b/signer/rules/deps/deps.go @@ -0,0 +1,21 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package deps contains the console JavaScript dependencies Go embedded. +package deps + +//go:generate go-bindata -nometadata -pkg deps -o bindata.go bignumber.js +//go:generate gofmt -w -s bindata.go diff --git a/signer/rules/rules.go b/signer/rules/rules.go new file mode 100644 index 000000000000..fa270436df3e --- /dev/null +++ b/signer/rules/rules.go @@ -0,0 +1,248 @@ +// 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 . + +package rules + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/signer/core" + "github.com/ethereum/go-ethereum/signer/rules/deps" + "github.com/ethereum/go-ethereum/signer/storage" + "github.com/robertkrimen/otto" +) + +var ( + BigNumber_JS = deps.MustAsset("bignumber.js") +) + +// consoleOutput is an override for the console.log and console.error methods to +// stream the output into the configured output stream instead of stdout. +func consoleOutput(call otto.FunctionCall) otto.Value { + output := []string{"JS:> "} + for _, argument := range call.ArgumentList { + output = append(output, fmt.Sprintf("%v", argument)) + } + fmt.Fprintln(os.Stdout, strings.Join(output, " ")) + return otto.Value{} +} + +// rulesetUi provides an implementation of SignerUI that evaluates a javascript +// file for each defined UI-method +type rulesetUi struct { + next core.SignerUI // The next handler, for manual processing + storage storage.Storage + credentials storage.Storage + jsRules string // The rules to use +} + +func NewRuleEvaluator(next core.SignerUI, jsbackend, credentialsBackend storage.Storage) (*rulesetUi, error) { + c := &rulesetUi{ + next: next, + storage: jsbackend, + credentials: credentialsBackend, + jsRules: "", + } + + return c, nil +} + +func (r *rulesetUi) Init(javascriptRules string) error { + r.jsRules = javascriptRules + return nil +} +func (r *rulesetUi) execute(jsfunc string, jsarg interface{}) (otto.Value, error) { + + // Instantiate a fresh vm engine every time + vm := otto.New() + // Set the native callbacks + consoleObj, _ := vm.Get("console") + consoleObj.Object().Set("log", consoleOutput) + consoleObj.Object().Set("error", consoleOutput) + vm.Set("storage", r.storage) + + // Load bootstrap libraries + script, err := vm.Compile("bignumber.js", BigNumber_JS) + if err != nil { + log.Warn("Failed loading libraries", "err", err) + return otto.UndefinedValue(), err + } + vm.Run(script) + + // Run the actual rule implementation + _, err = vm.Run(r.jsRules) + if err != nil { + log.Warn("Execution failed", "err", err) + return otto.UndefinedValue(), err + } + + // And the actual call + // All calls are objects with the parameters being keys in that object. + // To provide additional insulation between js and go, we serialize it into JSON on the Go-side, + // and deserialize it on the JS side. + + jsonbytes, err := json.Marshal(jsarg) + if err != nil { + log.Warn("failed marshalling data", "data", jsarg) + return otto.UndefinedValue(), err + } + // Now, we call foobar(JSON.parse()). + var call string + if len(jsonbytes) > 0 { + call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes)) + } else { + call = fmt.Sprintf("%v()", jsfunc) + } + return vm.Run(call) +} + +func (r *rulesetUi) checkApproval(jsfunc string, jsarg []byte, err error) (bool, error) { + if err != nil { + return false, err + } + v, err := r.execute(jsfunc, string(jsarg)) + if err != nil { + log.Info("error occurred during execution", "error", err) + return false, err + } + result, err := v.ToString() + if err != nil { + log.Info("error occurred during response unmarshalling", "error", err) + return false, err + } + if result == "Approve" { + log.Info("Op approved") + return true, nil + } else if result == "Reject" { + log.Info("Op rejected") + return false, nil + } + return false, fmt.Errorf("Unknown response") +} + +func (r *rulesetUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveTx", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveTx(request) + } + + if approved { + return core.SignTxResponse{ + Transaction: request.Transaction, + Approved: true, + Password: r.lookupPassword(request.Transaction.From.Address()), + }, + nil + } + return core.SignTxResponse{Approved: false}, err +} + +func (r *rulesetUi) lookupPassword(address common.Address) string { + return r.credentials.Get(strings.ToLower(address.String())) +} + +func (r *rulesetUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveSignData", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveSignData(request) + } + if approved { + return core.SignDataResponse{Approved: true, Password: r.lookupPassword(request.Address.Address())}, nil + } + return core.SignDataResponse{Approved: false, Password: ""}, err +} + +func (r *rulesetUi) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveExport", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveExport(request) + } + if approved { + return core.ExportResponse{Approved: true}, nil + } + return core.ExportResponse{Approved: false}, err +} + +func (r *rulesetUi) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + // This cannot be handled by rules, requires setting a password + // dispatch to next + return r.next.ApproveImport(request) +} + +func (r *rulesetUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + jsonreq, err := json.Marshal(request) + approved, err := r.checkApproval("ApproveListing", jsonreq, err) + if err != nil { + log.Info("Rule-based approval error, going to manual", "error", err) + return r.next.ApproveListing(request) + } + if approved { + return core.ListResponse{Accounts: request.Accounts}, nil + } + return core.ListResponse{}, err +} + +func (r *rulesetUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + // This cannot be handled by rules, requires setting a password + // dispatch to next + return r.next.ApproveNewAccount(request) +} + +func (r *rulesetUi) ShowError(message string) { + log.Error(message) + r.next.ShowError(message) +} + +func (r *rulesetUi) ShowInfo(message string) { + log.Info(message) + r.next.ShowInfo(message) +} +func (r *rulesetUi) OnSignerStartup(info core.StartupInfo) { + jsonInfo, err := json.Marshal(info) + if err != nil { + log.Warn("failed marshalling data", "data", info) + return + } + r.next.OnSignerStartup(info) + _, err = r.execute("OnSignerStartup", string(jsonInfo)) + if err != nil { + log.Info("error occurred during execution", "error", err) + } +} + +func (r *rulesetUi) OnApprovedTx(tx ethapi.SignTransactionResult) { + jsonTx, err := json.Marshal(tx) + if err != nil { + log.Warn("failed marshalling transaction", "tx", tx) + return + } + _, err = r.execute("OnApprovedTx", string(jsonTx)) + if err != nil { + log.Info("error occurred during execution", "error", err) + } +} diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go new file mode 100644 index 000000000000..6a0403500408 --- /dev/null +++ b/signer/rules/rules_test.go @@ -0,0 +1,631 @@ +// 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 . +// +package rules + +import ( + "fmt" + "math/big" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/signer/core" + "github.com/ethereum/go-ethereum/signer/storage" +) + +const JS = ` +/** +This is an example implementation of a Javascript rule file. + +When the signer receives a request over the external API, the corresponding method is evaluated. +Three things can happen: + +1. The method returns "Approve". This means the operation is permitted. +2. The method returns "Reject". This means the operation is rejected. +3. Anything else; other return values [*], method not implemented or exception occurred during processing. This means +that the operation will continue to manual processing, via the regular UI method chosen by the user. + +[*] Note: Future version of the ruleset may use more complex json-based returnvalues, making it possible to not +only respond Approve/Reject/Manual, but also modify responses. For example, choose to list only one, but not all +accounts in a list-request. The points above will continue to hold for non-json based responses ("Approve"/"Reject"). + +**/ + +function ApproveListing(request){ + console.log("In js approve listing"); + console.log(request.accounts[3].Address) + console.log(request.meta.Remote) + return "Approve" +} + +function ApproveTx(request){ + console.log("test"); + console.log("from"); + return "Reject"; +} + +function test(thing){ + console.log(thing.String()) +} + +` + +func mixAddr(a string) (*common.MixedcaseAddress, error) { + return common.NewMixedcaseAddressFromString(a) +} + +type alwaysDenyUi struct{} + +func (alwaysDenyUi) OnSignerStartup(info core.StartupInfo) { +} + +func (alwaysDenyUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil +} + +func (alwaysDenyUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + return core.SignDataResponse{Approved: false, Password: ""}, nil +} + +func (alwaysDenyUi) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + return core.ExportResponse{Approved: false}, nil +} + +func (alwaysDenyUi) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + return core.ImportResponse{Approved: false, OldPassword: "", NewPassword: ""}, nil +} + +func (alwaysDenyUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + return core.ListResponse{Accounts: nil}, nil +} + +func (alwaysDenyUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + return core.NewAccountResponse{Approved: false, Password: ""}, nil +} + +func (alwaysDenyUi) ShowError(message string) { + panic("implement me") +} + +func (alwaysDenyUi) ShowInfo(message string) { + panic("implement me") +} + +func (alwaysDenyUi) OnApprovedTx(tx ethapi.SignTransactionResult) { + panic("implement me") +} + +func initRuleEngine(js string) (*rulesetUi, error) { + r, err := NewRuleEvaluator(&alwaysDenyUi{}, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) + if err != nil { + return nil, fmt.Errorf("failed to create js engine: %v", err) + } + if err = r.Init(js); err != nil { + return nil, fmt.Errorf("failed to load bootstrap js: %v", err) + } + return r, nil +} + +func TestListRequest(t *testing.T) { + accs := make([]core.Account, 5) + + for i := range accs { + addr := fmt.Sprintf("000000000000000000000000000000000000000%x", i) + acc := core.Account{ + Address: common.BytesToAddress(common.Hex2Bytes(addr)), + URL: accounts.URL{Scheme: "test", Path: fmt.Sprintf("acc-%d", i)}, + } + accs[i] = acc + } + + js := `function ApproveListing(){ return "Approve" }` + + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + resp, err := r.ApproveListing(&core.ListRequest{ + Accounts: accs, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + }) + if len(resp.Accounts) != len(accs) { + t.Errorf("Expected check to resolve to 'Approve'") + } +} + +func TestSignTxRequest(t *testing.T) { + + js := ` + function ApproveTx(r){ + console.log("transaction.from", r.transaction.from); + console.log("transaction.to", r.transaction.to); + console.log("transaction.value", r.transaction.value); + console.log("transaction.nonce", r.transaction.nonce); + if(r.transaction.from.toLowerCase()=="0x0000000000000000000000000000000000001337"){ return "Approve"} + if(r.transaction.from.toLowerCase()=="0x000000000000000000000000000000000000dead"){ return "Reject"} + }` + + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + to, err := mixAddr("000000000000000000000000000000000000dead") + if err != nil { + t.Error(err) + return + } + from, err := mixAddr("0000000000000000000000000000000000001337") + + if err != nil { + t.Error(err) + return + } + fmt.Printf("to %v", to.Address().String()) + resp, err := r.ApproveTx(&core.SignTxRequest{ + Transaction: core.SendTxArgs{ + From: *from, + To: to}, + Callinfo: nil, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + }) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if !resp.Approved { + t.Errorf("Expected check to resolve to 'Approve'") + } +} + +type dummyUi struct { + calls []string +} + +func (d *dummyUi) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + d.calls = append(d.calls, "ApproveTx") + return core.SignTxResponse{}, core.ErrRequestDenied +} + +func (d *dummyUi) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + d.calls = append(d.calls, "ApproveSignData") + return core.SignDataResponse{}, core.ErrRequestDenied +} + +func (d *dummyUi) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + d.calls = append(d.calls, "ApproveExport") + return core.ExportResponse{}, core.ErrRequestDenied +} + +func (d *dummyUi) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + d.calls = append(d.calls, "ApproveImport") + return core.ImportResponse{}, core.ErrRequestDenied +} + +func (d *dummyUi) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + d.calls = append(d.calls, "ApproveListing") + return core.ListResponse{}, core.ErrRequestDenied +} + +func (d *dummyUi) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + d.calls = append(d.calls, "ApproveNewAccount") + return core.NewAccountResponse{}, core.ErrRequestDenied +} + +func (d *dummyUi) ShowError(message string) { + d.calls = append(d.calls, "ShowError") +} + +func (d *dummyUi) ShowInfo(message string) { + d.calls = append(d.calls, "ShowInfo") +} + +func (d *dummyUi) OnApprovedTx(tx ethapi.SignTransactionResult) { + d.calls = append(d.calls, "OnApprovedTx") +} +func (d *dummyUi) OnSignerStartup(info core.StartupInfo) { +} + +//TestForwarding tests that the rule-engine correctly dispatches requests to the next caller +func TestForwarding(t *testing.T) { + + js := "" + ui := &dummyUi{make([]string, 0)} + jsBackend := storage.NewEphemeralStorage() + credBackend := storage.NewEphemeralStorage() + r, err := NewRuleEvaluator(ui, jsBackend, credBackend) + if err != nil { + t.Fatalf("Failed to create js engine: %v", err) + } + if err = r.Init(js); err != nil { + t.Fatalf("Failed to load bootstrap js: %v", err) + } + r.ApproveSignData(nil) + r.ApproveTx(nil) + r.ApproveImport(nil) + r.ApproveNewAccount(nil) + r.ApproveListing(nil) + r.ApproveExport(nil) + r.ShowError("test") + r.ShowInfo("test") + + //This one is not forwarded + r.OnApprovedTx(ethapi.SignTransactionResult{}) + + expCalls := 8 + if len(ui.calls) != expCalls { + + t.Errorf("Expected %d forwarded calls, got %d: %s", expCalls, len(ui.calls), strings.Join(ui.calls, ",")) + + } + +} + +func TestMissingFunc(t *testing.T) { + r, err := initRuleEngine(JS) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + + _, err = r.execute("MissingMethod", "test") + + if err == nil { + t.Error("Expected error") + } + + approved, err := r.checkApproval("MissingMethod", nil, nil) + if err == nil { + t.Errorf("Expected missing method to yield error'") + } + if approved { + t.Errorf("Expected missing method to cause non-approval") + } + fmt.Printf("Err %v", err) + +} +func TestStorage(t *testing.T) { + + js := ` + function testStorage(){ + storage.Put("mykey", "myvalue") + a = storage.Get("mykey") + + storage.Put("mykey", ["a", "list"]) // Should result in "a,list" + a += storage.Get("mykey") + + + storage.Put("mykey", {"an": "object"}) // Should result in "[object Object]" + a += storage.Get("mykey") + + + storage.Put("mykey", JSON.stringify({"an": "object"})) // Should result in '{"an":"object"}' + a += storage.Get("mykey") + + a += storage.Get("missingkey") //Missing keys should result in empty string + storage.Put("","missing key==noop") // Can't store with 0-length key + a += storage.Get("") // Should result in '' + + var b = new BigNumber(2) + var c = new BigNumber(16)//"0xf0",16) + var d = b.plus(c) + console.log(d) + return a + } +` + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + + v, err := r.execute("testStorage", nil) + + if err != nil { + t.Errorf("Unexpected error %v", err) + } + + retval, err := v.ToString() + + if err != nil { + t.Errorf("Unexpected error %v", err) + } + exp := `myvaluea,list[object Object]{"an":"object"}` + if retval != exp { + t.Errorf("Unexpected data, expected '%v', got '%v'", exp, retval) + } + fmt.Printf("Err %v", err) + +} + +const ExampleTxWindow = ` + 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){ + console.log(r) + console.log(typeof(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)); + } + +` + +func dummyTx(value hexutil.Big) *core.SignTxRequest { + + to, _ := mixAddr("000000000000000000000000000000000000dead") + from, _ := mixAddr("000000000000000000000000000000000000dead") + n := hexutil.Uint64(3) + gas := hexutil.Uint64(21000) + gasPrice := hexutil.Big(*big.NewInt(2000000)) + + return &core.SignTxRequest{ + Transaction: core.SendTxArgs{ + From: *from, + To: to, + Value: value, + Nonce: n, + GasPrice: gasPrice, + Gas: gas, + }, + Callinfo: []core.ValidationInfo{ + {Typ: "Warning", Message: "All your base are bellong to us"}, + }, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + } +} +func dummyTxWithV(value uint64) *core.SignTxRequest { + + v := big.NewInt(0).SetUint64(value) + h := hexutil.Big(*v) + return dummyTx(h) +} +func dummySigned(value *big.Int) *types.Transaction { + to := common.HexToAddress("000000000000000000000000000000000000dead") + gas := uint64(21000) + gasPrice := big.NewInt(2000000) + data := make([]byte, 0) + return types.NewTransaction(3, to, value, gas, gasPrice, data) + +} +func TestLimitWindow(t *testing.T) { + + r, err := initRuleEngine(ExampleTxWindow) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + + // 0.3 ether: 429D069189E0000 wei + v := big.NewInt(0).SetBytes(common.Hex2Bytes("0429D069189E0000")) + h := hexutil.Big(*v) + // The first three should succeed + for i := 0; i < 3; i++ { + unsigned := dummyTx(h) + resp, err := r.ApproveTx(unsigned) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if !resp.Approved { + t.Errorf("Expected check to resolve to 'Approve'") + } + // Create a dummy signed transaction + + response := ethapi.SignTransactionResult{ + Tx: dummySigned(v), + Raw: common.Hex2Bytes("deadbeef"), + } + r.OnApprovedTx(response) + } + // Fourth should fail + resp, err := r.ApproveTx(dummyTx(h)) + if resp.Approved { + t.Errorf("Expected check to resolve to 'Reject'") + } + +} + +// dontCallMe is used as a next-handler that does not want to be called - it invokes test failure +type dontCallMe struct { + t *testing.T +} + +func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) { +} + +func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.SignTxResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.SignDataResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveExport(request *core.ExportRequest) (core.ExportResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.ExportResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveImport(request *core.ImportRequest) (core.ImportResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.ImportResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveListing(request *core.ListRequest) (core.ListResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.ListResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) { + d.t.Fatalf("Did not expect next-handler to be called") + return core.NewAccountResponse{}, core.ErrRequestDenied +} + +func (d *dontCallMe) ShowError(message string) { + d.t.Fatalf("Did not expect next-handler to be called") +} + +func (d *dontCallMe) ShowInfo(message string) { + d.t.Fatalf("Did not expect next-handler to be called") +} + +func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) { + d.t.Fatalf("Did not expect next-handler to be called") +} + +//TestContextIsCleared tests that the rule-engine does not retain variables over several requests. +// if it does, that would be bad since developers may rely on that to store data, +// instead of using the disk-based data storage +func TestContextIsCleared(t *testing.T) { + + js := ` + function ApproveTx(){ + if (typeof foobar == 'undefined') { + foobar = "Approve" + } + console.log(foobar) + if (foobar == "Approve"){ + foobar = "Reject" + }else{ + foobar = "Approve" + } + return foobar + } + ` + ui := &dontCallMe{t} + r, err := NewRuleEvaluator(ui, storage.NewEphemeralStorage(), storage.NewEphemeralStorage()) + if err != nil { + t.Fatalf("Failed to create js engine: %v", err) + } + if err = r.Init(js); err != nil { + t.Fatalf("Failed to load bootstrap js: %v", err) + } + tx := dummyTxWithV(0) + r1, err := r.ApproveTx(tx) + r2, err := r.ApproveTx(tx) + if r1.Approved != r2.Approved { + t.Errorf("Expected execution context to be cleared between executions") + } +} + +func TestSignData(t *testing.T) { + + js := `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 +}` + r, err := initRuleEngine(js) + if err != nil { + t.Errorf("Couldn't create evaluator %v", err) + return + } + message := []byte("baz bazonk foo") + hash, msg := core.SignHash(message) + raw := hexutil.Bytes(message) + addr, _ := mixAddr("0x694267f14675d7e1b9494fd8d72fefe1755710fa") + + fmt.Printf("address %v %v\n", addr.String(), addr.Original()) + resp, err := r.ApproveSignData(&core.SignDataRequest{ + Address: *addr, + Message: msg, + Hash: hash, + Meta: core.Metadata{Remote: "remoteip", Local: "localip", Scheme: "inproc"}, + Rawdata: raw, + }) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if !resp.Approved { + t.Fatalf("Expected approved") + } +} diff --git a/signer/storage/aes_gcm_storage.go b/signer/storage/aes_gcm_storage.go new file mode 100644 index 000000000000..1ac3475588a3 --- /dev/null +++ b/signer/storage/aes_gcm_storage.go @@ -0,0 +1,164 @@ +// 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 . +// +package storage + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/json" + "io" + "io/ioutil" + "os" + + "github.com/ethereum/go-ethereum/log" +) + +type storedCredential struct { + // The iv + Iv []byte `json:"iv"` + // The ciphertext + CipherText []byte `json:"c"` +} + +// AESEncryptedStorage is a storage type which is backed by a json-faile. The json-file contains +// key-value mappings, where the keys are _not_ encrypted, only the values are. +type AESEncryptedStorage struct { + // File to read/write credentials + filename string + // Key stored in base64 + key []byte +} + +// NewAESEncryptedStorage creates a new encrypted storage backed by the given file/key +func NewAESEncryptedStorage(filename string, key []byte) *AESEncryptedStorage { + return &AESEncryptedStorage{ + filename: filename, + key: key, + } +} + +// Put stores a value by key. 0-length keys results in no-op +func (s *AESEncryptedStorage) Put(key, value string) { + if len(key) == 0 { + return + } + data, err := s.readEncryptedStorage() + if err != nil { + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + return + } + ciphertext, iv, err := encrypt(s.key, []byte(value)) + if err != nil { + log.Warn("Failed to encrypt entry", "err", err) + return + } + encrypted := storedCredential{Iv: iv, CipherText: ciphertext} + data[key] = encrypted + if err = s.writeEncryptedStorage(data); err != nil { + log.Warn("Failed to write entry", "err", err) + } +} + +// Get returns the previously stored value, or the empty string if it does not exist or key is of 0-length +func (s *AESEncryptedStorage) Get(key string) string { + if len(key) == 0 { + return "" + } + data, err := s.readEncryptedStorage() + if err != nil { + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + return "" + } + encrypted, exist := data[key] + if !exist { + log.Warn("Key does not exist", "key", key) + return "" + } + entry, err := decrypt(s.key, encrypted.Iv, encrypted.CipherText) + if err != nil { + log.Warn("Failed to decrypt key", "key", key) + return "" + } + return string(entry) +} + +// readEncryptedStorage reads the file with encrypted creds +func (s *AESEncryptedStorage) readEncryptedStorage() (map[string]storedCredential, error) { + creds := make(map[string]storedCredential) + raw, err := ioutil.ReadFile(s.filename) + + if err != nil { + if os.IsNotExist(err) { + // Doesn't exist yet + return creds, nil + + } else { + log.Warn("Failed to read encrypted storage", "err", err, "file", s.filename) + } + } + if err = json.Unmarshal(raw, &creds); err != nil { + log.Warn("Failed to unmarshal encrypted storage", "err", err, "file", s.filename) + return nil, err + } + return creds, nil +} + +// writeEncryptedStorage write the file with encrypted creds +func (s *AESEncryptedStorage) writeEncryptedStorage(creds map[string]storedCredential) error { + raw, err := json.Marshal(creds) + if err != nil { + return err + } + if err = ioutil.WriteFile(s.filename, raw, 0600); err != nil { + return err + } + return nil +} + +func encrypt(key []byte, plaintext []byte) ([]byte, []byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, nil, err + } + aesgcm, err := cipher.NewGCM(block) + nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, nil, err + } + if err != nil { + return nil, nil, err + } + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + return ciphertext, nonce, nil +} + +func decrypt(key []byte, nonce []byte, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + return plaintext, nil +} diff --git a/signer/storage/aes_gcm_storage_test.go b/signer/storage/aes_gcm_storage_test.go new file mode 100644 index 000000000000..77804905acce --- /dev/null +++ b/signer/storage/aes_gcm_storage_test.go @@ -0,0 +1,115 @@ +// 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 . +// +package storage + +import ( + "bytes" + "fmt" + "io/ioutil" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/mattn/go-colorable" +) + +func TestEncryption(t *testing.T) { + // key := []byte("AES256Key-32Characters1234567890") + // plaintext := []byte(value) + key := []byte("AES256Key-32Characters1234567890") + plaintext := []byte("exampleplaintext") + + c, iv, err := encrypt(key, plaintext) + if err != nil { + t.Fatal(err) + } + fmt.Printf("Ciphertext %x, nonce %x\n", c, iv) + + p, err := decrypt(key, iv, c) + if err != nil { + t.Fatal(err) + } + fmt.Printf("Plaintext %v\n", string(p)) + if !bytes.Equal(plaintext, p) { + t.Errorf("Failed: expected plaintext recovery, got %v expected %v", string(plaintext), string(p)) + } +} + +func TestFileStorage(t *testing.T) { + + a := map[string]storedCredential{ + "secret": { + Iv: common.Hex2Bytes("cdb30036279601aeee60f16b"), + CipherText: common.Hex2Bytes("f311ac49859d7260c2c464c28ffac122daf6be801d3cfd3edcbde7e00c9ff74f"), + }, + "secret2": { + Iv: common.Hex2Bytes("afb8a7579bf971db9f8ceeed"), + CipherText: common.Hex2Bytes("2df87baf86b5073ef1f03e3cc738de75b511400f5465bb0ddeacf47ae4dc267d"), + }, + } + d, err := ioutil.TempDir("", "eth-encrypted-storage-test") + if err != nil { + t.Fatal(err) + } + stored := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + stored.writeEncryptedStorage(a) + read := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + creds, err := read.readEncryptedStorage() + if err != nil { + t.Fatal(err) + } + for k, v := range a { + if v2, exist := creds[k]; !exist { + t.Errorf("Missing entry %v", k) + } else { + if !bytes.Equal(v.CipherText, v2.CipherText) { + t.Errorf("Wrong ciphertext, expected %x got %x", v.CipherText, v2.CipherText) + } + if !bytes.Equal(v.Iv, v2.Iv) { + t.Errorf("Wrong iv") + } + } + } +} +func TestEnd2End(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + + d, err := ioutil.TempDir("", "eth-encrypted-storage-test") + if err != nil { + t.Fatal(err) + } + + s1 := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + s2 := &AESEncryptedStorage{ + filename: fmt.Sprintf("%v/vault.json", d), + key: []byte("AES256Key-32Characters1234567890"), + } + + s1.Put("bazonk", "foobar") + if v := s2.Get("bazonk"); v != "foobar" { + t.Errorf("Expected bazonk->foobar, got '%v'", v) + } +} diff --git a/signer/storage/storage.go b/signer/storage/storage.go new file mode 100644 index 000000000000..60f4e3892a31 --- /dev/null +++ b/signer/storage/storage.go @@ -0,0 +1,62 @@ +// 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 . +// + +package storage + +import ( + "fmt" +) + +type Storage interface { + // Put stores a value by key. 0-length keys results in no-op + Put(key, value string) + // Get returns the previously stored value, or the empty string if it does not exist or key is of 0-length + Get(key string) string +} + +// EphemeralStorage is an in-memory storage that does +// not persist values to disk. Mainly used for testing +type EphemeralStorage struct { + data map[string]string + namespace string +} + +func (s *EphemeralStorage) Put(key, value string) { + if len(key) == 0 { + return + } + fmt.Printf("storage: put %v -> %v\n", key, value) + s.data[key] = value +} + +func (s *EphemeralStorage) Get(key string) string { + if len(key) == 0 { + return "" + } + fmt.Printf("storage: get %v\n", key) + if v, exist := s.data[key]; exist { + return v + } + return "" +} + +func NewEphemeralStorage() Storage { + s := &EphemeralStorage{ + data: make(map[string]string), + } + return s +} diff --git a/swarm/fuse/fuse_dir.go b/swarm/fuse/fuse_dir.go index 91b236ae8af0..a7701985ebb6 100644 --- a/swarm/fuse/fuse_dir.go +++ b/swarm/fuse/fuse_dir.go @@ -19,12 +19,13 @@ package fuse import ( - "bazil.org/fuse" - "bazil.org/fuse/fs" - "golang.org/x/net/context" "os" "path/filepath" "sync" + + "bazil.org/fuse" + "bazil.org/fuse/fs" + "golang.org/x/net/context" ) var ( diff --git a/swarm/fuse/swarmfs.go b/swarm/fuse/swarmfs.go index 2493bdab19ba..e56d0ad4e39e 100644 --- a/swarm/fuse/swarmfs.go +++ b/swarm/fuse/swarmfs.go @@ -17,9 +17,10 @@ package fuse import ( - "github.com/ethereum/go-ethereum/swarm/api" "sync" "time" + + "github.com/ethereum/go-ethereum/swarm/api" ) const ( diff --git a/swarm/network/kademlia/address.go b/swarm/network/kademlia/address.go index 4c38a846f912..ef82d2e8b88d 100644 --- a/swarm/network/kademlia/address.go +++ b/swarm/network/kademlia/address.go @@ -51,7 +51,7 @@ func (a Address) Bin() string { /* Proximity(x, y) returns the proximity order of the MSB distance between x and y -The distance metric MSB(x, y) of two equal length byte sequences x an y is the +The distance metric MSB(x, y) of two equal length byte sequences x and y is the value of the binary integer cast of the x^y, ie., x and y bitwise xor-ed. the binary cast is big endian: most significant bit first (=MSB). diff --git a/swarm/storage/database.go b/swarm/storage/database.go index 2532490cc9cb..f2ceb94e46c4 100644 --- a/swarm/storage/database.go +++ b/swarm/storage/database.go @@ -22,7 +22,6 @@ package storage import ( "fmt" - "github.com/ethereum/go-ethereum/compression/rle" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/opt" @@ -31,8 +30,7 @@ import ( const openFileLimit = 128 type LDBDatabase struct { - db *leveldb.DB - comp bool + db *leveldb.DB } func NewLDBDatabase(file string) (*LDBDatabase, error) { @@ -42,16 +40,12 @@ func NewLDBDatabase(file string) (*LDBDatabase, error) { return nil, err } - database := &LDBDatabase{db: db, comp: false} + database := &LDBDatabase{db: db} return database, nil } func (self *LDBDatabase) Put(key []byte, value []byte) { - if self.comp { - value = rle.Compress(value) - } - err := self.db.Put(key, value, nil) if err != nil { fmt.Println("Error put", err) @@ -63,11 +57,6 @@ func (self *LDBDatabase) Get(key []byte) ([]byte, error) { if err != nil { return nil, err } - - if self.comp { - return rle.Decompress(dat) - } - return dat, nil } diff --git a/swarm/storage/dbstore.go b/swarm/storage/dbstore.go index 421bb061d523..1ff42a0c0577 100644 --- a/swarm/storage/dbstore.go +++ b/swarm/storage/dbstore.go @@ -54,7 +54,6 @@ const ( // key prefixes for leveldb storage kpIndex = 0 - kpData = 1 ) var ( diff --git a/swarm/storage/netstore.go b/swarm/storage/netstore.go index 5d4f17deb19a..0552b84efa65 100644 --- a/swarm/storage/netstore.go +++ b/swarm/storage/netstore.go @@ -83,11 +83,6 @@ func NewNetStore(hash SwarmHasher, lstore *LocalStore, cloud CloudStore, params } } -const ( - // maximum number of peers that a retrieved message is delivered to - requesterCount = 3 -) - var ( // timeout interval before retrieval is timed out searchTimeout = 3 * time.Second diff --git a/tests/block_test_util.go b/tests/block_test_util.go index beba48483322..579e783b1011 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -104,7 +104,7 @@ func (t *BlockTest) Run() error { return err } if gblock.Hash() != t.json.Genesis.Hash { - return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x\n", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) + return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) } if gblock.Root() != t.json.Genesis.StateRoot { return fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6]) diff --git a/tests/init.go b/tests/init.go index ff8ee7da1878..0bea5ccd6348 100644 --- a/tests/init.go +++ b/tests/init.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// This table defines supported forks and their chain config. +// Forks table defines supported forks and their chain config. var Forks = map[string]*params.ChainConfig{ "Frontier": { ChainId: big.NewInt(1), diff --git a/tests/init_test.go b/tests/init_test.go index fbb214b08c5b..26e919d24bb4 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -42,7 +42,7 @@ var ( difficultyTestDir = filepath.Join(baseDir, "BasicTests") ) -func readJson(reader io.Reader, value interface{}) error { +func readJSON(reader io.Reader, value interface{}) error { data, err := ioutil.ReadAll(reader) if err != nil { return fmt.Errorf("error reading JSON file: %v", err) @@ -57,14 +57,14 @@ func readJson(reader io.Reader, value interface{}) error { return nil } -func readJsonFile(fn string, value interface{}) error { +func readJSONFile(fn string, value interface{}) error { file, err := os.Open(fn) if err != nil { return err } defer file.Close() - err = readJson(file, value) + err = readJSON(file, value) if err != nil { return fmt.Errorf("%s in file %s", err.Error(), fn) } @@ -169,9 +169,8 @@ func (tm *testMatcher) checkFailure(t *testing.T, name string, err error) error if err != nil { t.Logf("error: %v", err) return nil - } else { - return fmt.Errorf("test succeeded unexpectedly") } + return fmt.Errorf("test succeeded unexpectedly") } return err } @@ -213,7 +212,7 @@ func (tm *testMatcher) runTestFile(t *testing.T, path, name string, runTest inte // Load the file as map[string]. m := makeMapFromTestFunc(runTest) - if err := readJsonFile(path, m.Addr().Interface()); err != nil { + if err := readJSONFile(path, m.Addr().Interface()); err != nil { t.Fatal(err) } diff --git a/tests/state_test.go b/tests/state_test.go index 9ca5f18303fc..adec4feb2be8 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -36,14 +36,7 @@ func TestState(t *testing.T) { st.skipLoad(`^stTransactionTest/zeroSigTransa[^/]*\.json`) // EIP-86 is not supported yet // Expected failures: st.fails(`^stRevertTest/RevertPrecompiledTouch\.json/EIP158`, "bug in test") - st.fails(`^stRevertTest/RevertPrefoundEmptyOOG\.json/EIP158`, "bug in test") st.fails(`^stRevertTest/RevertPrecompiledTouch\.json/Byzantium`, "bug in test") - st.fails(`^stRevertTest/RevertPrefoundEmptyOOG\.json/Byzantium`, "bug in test") - st.fails(`^stRandom2/randomStatetest64[45]\.json/(EIP150|Frontier|Homestead)/.*`, "known bug #15119") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/EIP158/2`, "known bug ") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/EIP158/3`, "known bug ") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/Byzantium/2`, "known bug ") - st.fails(`^stCreateTest/TransactionCollisionToEmpty\.json/Byzantium/3`, "known bug ") st.walk(t, stateTestDir, func(t *testing.T, name string, test *StateTest) { for _, subtest := range test.Subtests() { diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 2028d2a2787b..8c3dac088c22 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -72,9 +72,8 @@ func (tt *TransactionTest) Run(config *params.ChainConfig) error { if err := rlp.DecodeBytes(tt.json.RLP, tx); err != nil { if tt.json.Transaction == nil { return nil - } else { - return fmt.Errorf("RLP decoding failed: %v", err) } + return fmt.Errorf("RLP decoding failed: %v", err) } // Check sender derivation. signer := types.MakeSigner(config, new(big.Int).SetUint64(uint64(tt.json.BlockNumber))) diff --git a/trie/iterator.go b/trie/iterator.go index 76146c0d64f8..3bae8e186bad 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -303,7 +303,7 @@ func (it *nodeIterator) push(state *nodeIteratorState, parentIndex *int, path [] it.path = path it.stack = append(it.stack, state) if parentIndex != nil { - *parentIndex += 1 + *parentIndex++ } } @@ -380,7 +380,7 @@ func (it *differenceIterator) Next(bool) bool { if !it.b.Next(true) { return false } - it.count += 1 + it.count++ if it.eof { // a has reached eof, so we just return all elements from b @@ -395,7 +395,7 @@ func (it *differenceIterator) Next(bool) bool { it.eof = true return true } - it.count += 1 + it.count++ case 1: // b is before a return true @@ -405,12 +405,12 @@ func (it *differenceIterator) Next(bool) bool { if !it.b.Next(hasHash) { return false } - it.count += 1 + it.count++ if !it.a.Next(hasHash) { it.eof = true return true } - it.count += 1 + it.count++ } } } @@ -504,14 +504,14 @@ func (it *unionIterator) Next(descend bool) bool { skipped := heap.Pop(it.items).(NodeIterator) // Skip the whole subtree if the nodes have hashes; otherwise just skip this node if skipped.Next(skipped.Hash() == common.Hash{}) { - it.count += 1 + it.count++ // If there are more elements, push the iterator back on the heap heap.Push(it.items, skipped) } } if least.Next(descend) { - it.count += 1 + it.count++ heap.Push(it.items, least) } diff --git a/trie/node.go b/trie/node.go index a7697fc0c656..02815042c6da 100644 --- a/trie/node.go +++ b/trie/node.go @@ -123,17 +123,17 @@ func decodeNode(hash, buf []byte, cachegen uint16) (node, error) { } switch c, _ := rlp.CountValues(elems); c { case 2: - n, err := decodeShort(hash, buf, elems, cachegen) + n, err := decodeShort(hash, elems, cachegen) return n, wrapError(err, "short") case 17: - n, err := decodeFull(hash, buf, elems, cachegen) + n, err := decodeFull(hash, elems, cachegen) return n, wrapError(err, "full") default: return nil, fmt.Errorf("invalid number of list elements: %v", c) } } -func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { +func decodeShort(hash, elems []byte, cachegen uint16) (node, error) { kbuf, rest, err := rlp.SplitString(elems) if err != nil { return nil, err @@ -155,7 +155,7 @@ func decodeShort(hash, buf, elems []byte, cachegen uint16) (node, error) { return &shortNode{key, r, flag}, nil } -func decodeFull(hash, buf, elems []byte, cachegen uint16) (*fullNode, error) { +func decodeFull(hash, elems []byte, cachegen uint16) (*fullNode, error) { n := &fullNode{flags: nodeFlag{hash: hash, gen: cachegen}} for i := 0; i < 16; i++ { cld, rest, err := decodeRef(elems, cachegen) diff --git a/trie/sync.go b/trie/sync.go index b573a9f73245..4ae975d0428c 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -212,7 +212,7 @@ func (s *TrieSync) Process(results []SyncResult) (bool, int, error) { } // Commit flushes the data stored in the internal membatch out to persistent -// storage, returning th enumber of items written and any occurred error. +// storage, returning the number of items written and any occurred error. func (s *TrieSync) Commit(dbw ethdb.Putter) (int, error) { // Dump the membatch into a database dbw for i, key := range s.membatch.order { diff --git a/vendor/github.com/elastic/gosigar/CHANGELOG.md b/vendor/github.com/elastic/gosigar/CHANGELOG.md index 12695e10ef11..45262e7b8dbd 100644 --- a/vendor/github.com/elastic/gosigar/CHANGELOG.md +++ b/vendor/github.com/elastic/gosigar/CHANGELOG.md @@ -8,10 +8,21 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed +- Added missing runtime import for FreeBSD. #104 + ### Changed ### Deprecated +## [0.9.0] + +### Added +- Added support for huge TLB pages on Linux #97 +- Added support for big endian platform #100 + +### Fixed +- Add missing method for OpenBSD #99 + ## [0.8.0] ### Added diff --git a/vendor/github.com/elastic/gosigar/README.md b/vendor/github.com/elastic/gosigar/README.md index 2482620a834a..ecdfc1c3c56a 100644 --- a/vendor/github.com/elastic/gosigar/README.md +++ b/vendor/github.com/elastic/gosigar/README.md @@ -26,6 +26,7 @@ The features vary by operating system. | FDUsage | X | | | | X | | FileSystemList | X | X | X | X | X | | FileSystemUsage | X | X | X | X | X | +| HugeTLBPages | X | | | | | | LoadAverage | X | X | | X | X | | Mem | X | X | X | X | X | | ProcArgs | X | X | X | | X | diff --git a/vendor/github.com/elastic/gosigar/concrete_sigar.go b/vendor/github.com/elastic/gosigar/concrete_sigar.go index 685aa6dedd1d..e3ee80a980b9 100644 --- a/vendor/github.com/elastic/gosigar/concrete_sigar.go +++ b/vendor/github.com/elastic/gosigar/concrete_sigar.go @@ -62,6 +62,12 @@ func (c *ConcreteSigar) GetSwap() (Swap, error) { return s, err } +func (c *ConcreteSigar) GetHugeTLBPages() (HugeTLBPages, error) { + p := HugeTLBPages{} + err := p.Get() + return p, err +} + func (c *ConcreteSigar) GetFileSystemUsage(path string) (FileSystemUsage, error) { f := FileSystemUsage{} err := f.Get(path) diff --git a/vendor/github.com/elastic/gosigar/sigar_darwin.go b/vendor/github.com/elastic/gosigar/sigar_darwin.go index f989f51608b4..a90b998c2ef6 100644 --- a/vendor/github.com/elastic/gosigar/sigar_darwin.go +++ b/vendor/github.com/elastic/gosigar/sigar_darwin.go @@ -91,6 +91,10 @@ func (self *Swap) Get() error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (self *Cpu) Get() error { var count C.mach_msg_type_number_t = C.HOST_CPU_LOAD_INFO_COUNT var cpuload C.host_cpu_load_info_data_t diff --git a/vendor/github.com/elastic/gosigar/sigar_freebsd.go b/vendor/github.com/elastic/gosigar/sigar_freebsd.go index 602b4a0aad91..9b2af639b6d2 100644 --- a/vendor/github.com/elastic/gosigar/sigar_freebsd.go +++ b/vendor/github.com/elastic/gosigar/sigar_freebsd.go @@ -4,6 +4,7 @@ package gosigar import ( "io/ioutil" + "runtime" "strconv" "strings" "unsafe" @@ -97,6 +98,10 @@ func (self *ProcFDUsage) Get(pid int) error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func parseCpuStat(self *Cpu, line string) error { fields := strings.Fields(line) diff --git a/vendor/github.com/elastic/gosigar/sigar_interface.go b/vendor/github.com/elastic/gosigar/sigar_interface.go index a956af604aff..df79ae08d2e8 100644 --- a/vendor/github.com/elastic/gosigar/sigar_interface.go +++ b/vendor/github.com/elastic/gosigar/sigar_interface.go @@ -26,6 +26,7 @@ type Sigar interface { GetLoadAverage() (LoadAverage, error) GetMem() (Mem, error) GetSwap() (Swap, error) + GetHugeTLBPages(HugeTLBPages, error) GetFileSystemUsage(string) (FileSystemUsage, error) GetFDUsage() (FDUsage, error) GetRusage(who int) (Rusage, error) @@ -82,6 +83,15 @@ type Swap struct { Free uint64 } +type HugeTLBPages struct { + Total uint64 + Free uint64 + Reserved uint64 + Surplus uint64 + DefaultSize uint64 + TotalAllocatedSize uint64 +} + type CpuList struct { List []Cpu } diff --git a/vendor/github.com/elastic/gosigar/sigar_linux.go b/vendor/github.com/elastic/gosigar/sigar_linux.go index cb1d3525b51b..09f2e30b2f03 100644 --- a/vendor/github.com/elastic/gosigar/sigar_linux.go +++ b/vendor/github.com/elastic/gosigar/sigar_linux.go @@ -45,6 +45,30 @@ func (self *FDUsage) Get() error { }) } +func (self *HugeTLBPages) Get() error { + table, err := parseMeminfo() + if err != nil { + return err + } + + self.Total, _ = table["HugePages_Total"] + self.Free, _ = table["HugePages_Free"] + self.Reserved, _ = table["HugePages_Rsvd"] + self.Surplus, _ = table["HugePages_Surp"] + self.DefaultSize, _ = table["Hugepagesize"] + + if totalSize, found := table["Hugetlb"]; found { + self.TotalAllocatedSize = totalSize + } else { + // If Hugetlb is not present, or huge pages of different sizes + // are used, this figure can be unaccurate. + // TODO (jsoriano): Extract information from /sys/kernel/mm/hugepages too + self.TotalAllocatedSize = (self.Total - self.Free + self.Reserved) * self.DefaultSize + } + + return nil +} + func (self *ProcFDUsage) Get(pid int) error { err := readFile(procFileName(pid, "limits"), func(line string) bool { if strings.HasPrefix(line, "Max open files") { diff --git a/vendor/github.com/elastic/gosigar/sigar_linux_common.go b/vendor/github.com/elastic/gosigar/sigar_linux_common.go index 8e5e7856f690..7ca649762225 100644 --- a/vendor/github.com/elastic/gosigar/sigar_linux_common.go +++ b/vendor/github.com/elastic/gosigar/sigar_linux_common.go @@ -379,12 +379,16 @@ func parseMeminfo() (map[string]uint64, error) { return true // skip on errors } - num := strings.TrimLeft(fields[1], " ") - val, err := strtoull(strings.Fields(num)[0]) + valueUnit := strings.Fields(fields[1]) + value, err := strtoull(valueUnit[0]) if err != nil { return true // skip on errors } - table[fields[0]] = val * 1024 //in bytes + + if len(valueUnit) > 1 && valueUnit[1] == "kB" { + value *= 1024 + } + table[fields[0]] = value return true }) @@ -420,8 +424,18 @@ func procFileName(pid int, name string) string { return Procd + "/" + strconv.Itoa(pid) + "/" + name } -func readProcFile(pid int, name string) ([]byte, error) { +func readProcFile(pid int, name string) (content []byte, err error) { path := procFileName(pid, name) + + // Panics have been reported when reading proc files, let's recover and + // report the path if this happens + // See https://github.com/elastic/beats/issues/6692 + defer func() { + if r := recover(); r != nil { + content = nil + err = fmt.Errorf("recovered panic when reading proc file '%s': %v", path, r) + } + }() contents, err := ioutil.ReadFile(path) if err != nil { diff --git a/vendor/github.com/elastic/gosigar/sigar_openbsd.go b/vendor/github.com/elastic/gosigar/sigar_openbsd.go index 4f1383a6ba93..e4371b8b6884 100644 --- a/vendor/github.com/elastic/gosigar/sigar_openbsd.go +++ b/vendor/github.com/elastic/gosigar/sigar_openbsd.go @@ -294,6 +294,10 @@ func (self *Swap) Get() error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (self *Cpu) Get() error { load := [C.CPUSTATES]C.long{C.CP_USER, C.CP_NICE, C.CP_SYS, C.CP_INTR, C.CP_IDLE} @@ -381,6 +385,10 @@ func (self *ProcFDUsage) Get(pid int) error { return ErrNotImplemented{runtime.GOOS} } +func (self *Rusage) Get(pid int) error { + return ErrNotImplemented{runtime.GOOS} +} + func fillCpu(cpu *Cpu, load [C.CPUSTATES]C.long) { cpu.User = uint64(load[0]) cpu.Nice = uint64(load[1]) diff --git a/vendor/github.com/elastic/gosigar/sigar_stub.go b/vendor/github.com/elastic/gosigar/sigar_stub.go index 0b858f1c0cfd..de9565aec4f5 100644 --- a/vendor/github.com/elastic/gosigar/sigar_stub.go +++ b/vendor/github.com/elastic/gosigar/sigar_stub.go @@ -22,6 +22,10 @@ func (s *Swap) Get() error { return ErrNotImplemented{runtime.GOOS} } +func (s *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (f *FDUsage) Get() error { return ErrNotImplemented{runtime.GOOS} } diff --git a/vendor/github.com/elastic/gosigar/sigar_windows.go b/vendor/github.com/elastic/gosigar/sigar_windows.go index 0cdf928d16ad..c2b54d8d7f77 100644 --- a/vendor/github.com/elastic/gosigar/sigar_windows.go +++ b/vendor/github.com/elastic/gosigar/sigar_windows.go @@ -120,6 +120,10 @@ func (self *Swap) Get() error { return nil } +func (self *HugeTLBPages) Get() error { + return ErrNotImplemented{runtime.GOOS} +} + func (self *Cpu) Get() error { idle, kernel, user, err := windows.GetSystemTimes() if err != nil { diff --git a/vendor/github.com/fjl/memsize/LICENSE b/vendor/github.com/fjl/memsize/LICENSE new file mode 100644 index 000000000000..8b8045641902 --- /dev/null +++ b/vendor/github.com/fjl/memsize/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Felix Lange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fjl/memsize/bitmap.go b/vendor/github.com/fjl/memsize/bitmap.go new file mode 100644 index 000000000000..47799ea8d342 --- /dev/null +++ b/vendor/github.com/fjl/memsize/bitmap.go @@ -0,0 +1,119 @@ +package memsize + +import ( + "math/bits" +) + +const ( + uintptrBits = 32 << (uint64(^uintptr(0)) >> 63) + uintptrBytes = uintptrBits / 8 + bmBlockRange = 1 * 1024 * 1024 // bytes covered by bmBlock + bmBlockWords = bmBlockRange / uintptrBits +) + +// bitmap is a sparse bitmap. +type bitmap struct { + blocks map[uintptr]*bmBlock +} + +func newBitmap() *bitmap { + return &bitmap{make(map[uintptr]*bmBlock)} +} + +// markRange sets n consecutive bits starting at addr. +func (b *bitmap) markRange(addr, n uintptr) { + for end := addr + n; addr < end; { + block, baddr := b.block(addr) + for i := baddr; i < bmBlockRange && addr < end; i++ { + block.mark(i) + addr++ + } + } +} + +// isMarked returns the value of the bit at the given address. +func (b *bitmap) isMarked(addr uintptr) bool { + block, baddr := b.block(addr) + return block.isMarked(baddr) +} + +// countRange returns the number of set bits in the range (addr,addr+n). +func (b *bitmap) countRange(addr, n uintptr) uintptr { + c := uintptr(0) + for end := addr + n; addr < end; { + block, baddr := b.block(addr) + bend := uintptr(bmBlockRange - 1) + if baddr+(end-addr) < bmBlockRange { + bend = baddr + (end - addr) + } + c += uintptr(block.count(baddr, bend)) + // Move addr to next block. + addr += bmBlockRange - baddr + } + return c +} + +// block finds the block corresponding to the given memory address. +// It also returns the block's starting address. +func (b *bitmap) block(addr uintptr) (*bmBlock, uintptr) { + index := addr / bmBlockRange + block := b.blocks[index] + if block == nil { + block = new(bmBlock) + b.blocks[index] = block + } + return block, addr % bmBlockRange +} + +// size returns the sum of the byte sizes of all blocks. +func (b *bitmap) size() uintptr { + return uintptr(len(b.blocks)) * bmBlockWords * uintptrBytes +} + +// utilization returns the mean percentage of one bits across all blocks. +func (b *bitmap) utilization() float32 { + var avg float32 + for _, block := range b.blocks { + avg += float32(block.count(0, bmBlockRange-1)) / float32(bmBlockRange) + } + return avg / float32(len(b.blocks)) +} + +// bmBlock is a bitmap block. +type bmBlock [bmBlockWords]uintptr + +// mark sets the i'th bit to one. +func (b *bmBlock) mark(i uintptr) { + b[i/uintptrBits] |= 1 << (i % uintptrBits) +} + +// isMarked returns the value of the i'th bit. +func (b *bmBlock) isMarked(i uintptr) bool { + return (b[i/uintptrBits] & (1 << (i % uintptrBits))) != 0 +} + +// count returns the number of set bits in the range (start,end). +func (b *bmBlock) count(start, end uintptr) (count int) { + br := b[start/uintptrBits : end/uintptrBits+1] + for i, w := range br { + if i == 0 { + w &= blockmask(start) + } + if i == len(br)-1 { + w &^= blockmask(end) + } + count += onesCountPtr(w) + } + return count +} + +func blockmask(x uintptr) uintptr { + return ^uintptr(0) << (x % uintptrBits) +} + +func onesCountPtr(x uintptr) int { + if uintptrBits == 64 { + return bits.OnesCount64(uint64(x)) + } + return bits.OnesCount32(uint32(x)) +} diff --git a/vendor/github.com/fjl/memsize/doc.go b/vendor/github.com/fjl/memsize/doc.go new file mode 100644 index 000000000000..640cfba5ebf8 --- /dev/null +++ b/vendor/github.com/fjl/memsize/doc.go @@ -0,0 +1,16 @@ +/* +Package memsize computes the size of your object graph. + +So you made a spiffy algorithm and it works really well, but geez it's using +way too much memory. Where did it all go? memsize to the rescue! + +To get started, find a value that references all your objects and scan it. +This traverses the graph, counting sizes per type. + + sizes := memsize.Scan(myValue) + fmt.Println(sizes.Total) + +memsize can handle cycles just fine and tracks both private and public struct fields. +Unfortunately function closures cannot be inspected in any way. +*/ +package memsize diff --git a/vendor/github.com/fjl/memsize/memsize.go b/vendor/github.com/fjl/memsize/memsize.go new file mode 100644 index 000000000000..2664e87c4604 --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsize.go @@ -0,0 +1,243 @@ +package memsize + +import ( + "bytes" + "fmt" + "reflect" + "sort" + "strings" + "text/tabwriter" + "unsafe" +) + +// Scan traverses all objects reachable from v and counts how much memory +// is used per type. The value must be a non-nil pointer to any value. +func Scan(v interface{}) Sizes { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + panic("value to scan must be non-nil pointer") + } + + stopTheWorld("memsize scan") + defer startTheWorld() + + ctx := newContext() + ctx.scan(invalidAddr, rv, false) + ctx.s.BitmapSize = ctx.seen.size() + ctx.s.BitmapUtilization = ctx.seen.utilization() + return *ctx.s +} + +// Sizes is the result of a scan. +type Sizes struct { + Total uintptr + ByType map[reflect.Type]*TypeSize + // Internal stats (for debugging) + BitmapSize uintptr + BitmapUtilization float32 +} + +type TypeSize struct { + Total uintptr + Count uintptr +} + +func newSizes() *Sizes { + return &Sizes{ByType: make(map[reflect.Type]*TypeSize)} +} + +// Report returns a human-readable report. +func (s Sizes) Report() string { + type typLine struct { + name string + count uintptr + total uintptr + } + tab := []typLine{{"ALL", 0, s.Total}} + for _, typ := range s.ByType { + tab[0].count += typ.Count + } + maxname := 0 + for typ, s := range s.ByType { + line := typLine{typ.String(), s.Count, s.Total} + tab = append(tab, line) + if len(line.name) > maxname { + maxname = len(line.name) + } + } + sort.Slice(tab, func(i, j int) bool { return tab[i].total > tab[j].total }) + + buf := new(bytes.Buffer) + w := tabwriter.NewWriter(buf, 0, 0, 0, ' ', tabwriter.AlignRight) + for _, line := range tab { + namespace := strings.Repeat(" ", maxname-len(line.name)) + fmt.Fprintf(w, "%s%s\t %v\t %s\t\n", line.name, namespace, line.count, HumanSize(line.total)) + } + w.Flush() + return buf.String() +} + +// addValue is called during scan and adds the memory of given object. +func (s *Sizes) addValue(v reflect.Value, size uintptr) { + s.Total += size + rs := s.ByType[v.Type()] + if rs == nil { + rs = new(TypeSize) + s.ByType[v.Type()] = rs + } + rs.Total += size + rs.Count++ +} + +type context struct { + // We track previously scanned objects to prevent infinite loops + // when scanning cycles and to prevent counting objects more than once. + seen *bitmap + tc typCache + s *Sizes +} + +func newContext() *context { + return &context{seen: newBitmap(), tc: make(typCache), s: newSizes()} +} + +// scan walks all objects below v, determining their size. All scan* functions return the +// amount of 'extra' memory (e.g. slice data) that is referenced by the object. +func (c *context) scan(addr address, v reflect.Value, add bool) (extraSize uintptr) { + size := v.Type().Size() + var marked uintptr + if addr.valid() { + marked = c.seen.countRange(uintptr(addr), size) + if marked == size { + return 0 // Skip if we have already seen the whole object. + } + c.seen.markRange(uintptr(addr), size) + } + // fmt.Printf("%v: %v ⮑ (marked %d)\n", addr, v.Type(), marked) + if c.tc.needScan(v.Type()) { + extraSize = c.scanContent(addr, v) + } + // fmt.Printf("%v: %v %d (add %v, size %d, marked %d, extra %d)\n", addr, v.Type(), size+extraSize, add, v.Type().Size(), marked, extraSize) + if add { + size -= marked + size += extraSize + c.s.addValue(v, size) + } + return extraSize +} + +func (c *context) scanContent(addr address, v reflect.Value) uintptr { + switch v.Kind() { + case reflect.Array: + return c.scanArray(addr, v) + case reflect.Chan: + return c.scanChan(v) + case reflect.Func: + // can't do anything here + return 0 + case reflect.Interface: + return c.scanInterface(v) + case reflect.Map: + return c.scanMap(v) + case reflect.Ptr: + if !v.IsNil() { + c.scan(address(v.Pointer()), v.Elem(), true) + } + return 0 + case reflect.Slice: + return c.scanSlice(v) + case reflect.String: + return uintptr(v.Len()) + case reflect.Struct: + return c.scanStruct(addr, v) + default: + unhandledKind(v.Kind()) + return 0 + } +} + +func (c *context) scanChan(v reflect.Value) uintptr { + etyp := v.Type().Elem() + extra := uintptr(0) + if c.tc.needScan(etyp) { + // Scan the channel buffer. This is unsafe but doesn't race because + // the world is stopped during scan. + hchan := unsafe.Pointer(v.Pointer()) + for i := uint(0); i < uint(v.Cap()); i++ { + addr := chanbuf(hchan, i) + elem := reflect.NewAt(etyp, addr).Elem() + extra += c.scanContent(address(addr), elem) + } + } + return uintptr(v.Cap())*etyp.Size() + extra +} + +func (c *context) scanStruct(base address, v reflect.Value) uintptr { + extra := uintptr(0) + for i := 0; i < v.NumField(); i++ { + f := v.Type().Field(i) + if c.tc.needScan(f.Type) { + addr := base.addOffset(f.Offset) + extra += c.scanContent(addr, v.Field(i)) + } + } + return extra +} + +func (c *context) scanArray(addr address, v reflect.Value) uintptr { + esize := v.Type().Elem().Size() + extra := uintptr(0) + for i := 0; i < v.Len(); i++ { + extra += c.scanContent(addr, v.Index(i)) + addr = addr.addOffset(esize) + } + return extra +} + +func (c *context) scanSlice(v reflect.Value) uintptr { + slice := v.Slice(0, v.Cap()) + esize := slice.Type().Elem().Size() + base := slice.Pointer() + // Add size of the unscanned portion of the backing array to extra. + blen := uintptr(slice.Len()) * esize + marked := c.seen.countRange(base, blen) + extra := blen - marked + c.seen.markRange(uintptr(base), blen) + if c.tc.needScan(slice.Type().Elem()) { + // Elements may contain pointers, scan them individually. + addr := address(base) + for i := 0; i < slice.Len(); i++ { + extra += c.scanContent(addr, slice.Index(i)) + addr = addr.addOffset(esize) + } + } + return extra +} + +func (c *context) scanMap(v reflect.Value) uintptr { + var ( + typ = v.Type() + len = uintptr(v.Len()) + extra = uintptr(0) + ) + if c.tc.needScan(typ.Key()) || c.tc.needScan(typ.Elem()) { + for _, k := range v.MapKeys() { + extra += c.scan(invalidAddr, k, false) + extra += c.scan(invalidAddr, v.MapIndex(k), false) + } + } + return len*typ.Key().Size() + len*typ.Elem().Size() + extra +} + +func (c *context) scanInterface(v reflect.Value) uintptr { + elem := v.Elem() + if !elem.IsValid() { + return 0 // nil interface + } + c.scan(invalidAddr, elem, false) + if !c.tc.isPointer(elem.Type()) { + // Account for non-pointer size of the value. + return elem.Type().Size() + } + return 0 +} diff --git a/vendor/github.com/fjl/memsize/memsizeui/template.go b/vendor/github.com/fjl/memsize/memsizeui/template.go new file mode 100644 index 000000000000..b60fe6ba549a --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsizeui/template.go @@ -0,0 +1,106 @@ +package memsizeui + +import ( + "html/template" + "strconv" + "sync" + + "github.com/fjl/memsize" +) + +var ( + base *template.Template // the "base" template + baseInitOnce sync.Once +) + +func baseInit() { + base = template.Must(template.New("base").Parse(` + + + + memsize + + + + {{template "content" .}} + +`)) + + base.Funcs(template.FuncMap{ + "quote": strconv.Quote, + "humansize": memsize.HumanSize, + }) + + template.Must(base.New("rootbuttons").Parse(` +Overview +{{- range $root := .Roots -}} +
+ +
+{{- end -}}`)) +} + +func contentTemplate(source string) *template.Template { + baseInitOnce.Do(baseInit) + t := template.Must(base.Clone()) + template.Must(t.New("content").Parse(source)) + return t +} + +var rootTemplate = contentTemplate(` +

Memsize

+{{template "rootbuttons" .}} +
+

Reports

+ +`) + +var notFoundTemplate = contentTemplate(` +

{{.Data}}

+{{template "rootbuttons" .}} +`) + +var reportTemplate = contentTemplate(` +{{- $report := .Data -}} +

Memsize Report {{$report.ID}}

+
+ Overview + +
+
+Root: {{quote $report.RootName}}
+Date: {{$report.Date}}
+Duration: {{$report.Duration}}
+Bitmap Size: {{$report.Sizes.BitmapSize | humansize}}
+Bitmap Utilization: {{$report.Sizes.BitmapUtilization}}
+
+
+
+{{$report.Sizes.Report}}
+
+`) diff --git a/vendor/github.com/fjl/memsize/memsizeui/ui.go b/vendor/github.com/fjl/memsize/memsizeui/ui.go new file mode 100644 index 000000000000..c48fc53f7f6c --- /dev/null +++ b/vendor/github.com/fjl/memsize/memsizeui/ui.go @@ -0,0 +1,153 @@ +package memsizeui + +import ( + "bytes" + "fmt" + "html/template" + "net/http" + "reflect" + "sort" + "strings" + "sync" + "time" + + "github.com/fjl/memsize" +) + +type Handler struct { + init sync.Once + mux http.ServeMux + mu sync.Mutex + reports map[int]Report + roots map[string]interface{} + reportID int +} + +type Report struct { + ID int + Date time.Time + Duration time.Duration + RootName string + Sizes memsize.Sizes +} + +type templateInfo struct { + Roots []string + Reports map[int]Report + PathDepth int + Data interface{} +} + +func (ti *templateInfo) Link(path ...string) string { + prefix := strings.Repeat("../", ti.PathDepth) + return prefix + strings.Join(path, "") +} + +func (h *Handler) Add(name string, v interface{}) { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + panic("root must be non-nil pointer") + } + h.mu.Lock() + if h.roots == nil { + h.roots = make(map[string]interface{}) + } + h.roots[name] = v + h.mu.Unlock() +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.init.Do(func() { + h.reports = make(map[int]Report) + h.mux.HandleFunc("/", h.handleRoot) + h.mux.HandleFunc("/scan", h.handleScan) + h.mux.HandleFunc("/report/", h.handleReport) + }) + h.mux.ServeHTTP(w, r) +} + +func (h *Handler) templateInfo(r *http.Request, data interface{}) *templateInfo { + h.mu.Lock() + roots := make([]string, 0, len(h.roots)) + for name := range h.roots { + roots = append(roots, name) + } + h.mu.Unlock() + sort.Strings(roots) + + return &templateInfo{ + Roots: roots, + Reports: h.reports, + PathDepth: strings.Count(r.URL.Path, "/") - 1, + Data: data, + } +} + +func (h *Handler) handleRoot(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + serveHTML(w, rootTemplate, http.StatusOK, h.templateInfo(r, nil)) +} + +func (h *Handler) handleScan(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "invalid HTTP method, want POST", http.StatusMethodNotAllowed) + return + } + ti := h.templateInfo(r, "Unknown root") + id, ok := h.scan(r.URL.Query().Get("root")) + if !ok { + serveHTML(w, notFoundTemplate, http.StatusNotFound, ti) + return + } + w.Header().Add("Location", ti.Link(fmt.Sprintf("report/%d", id))) + w.WriteHeader(http.StatusSeeOther) +} + +func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) { + var id int + fmt.Sscan(strings.TrimPrefix(r.URL.Path, "/report/"), &id) + h.mu.Lock() + report, ok := h.reports[id] + h.mu.Unlock() + + if !ok { + serveHTML(w, notFoundTemplate, http.StatusNotFound, h.templateInfo(r, "Report not found")) + } else { + serveHTML(w, reportTemplate, http.StatusOK, h.templateInfo(r, report)) + } +} + +func (h *Handler) scan(root string) (int, bool) { + h.mu.Lock() + defer h.mu.Unlock() + + val, ok := h.roots[root] + if !ok { + return 0, false + } + id := h.reportID + start := time.Now() + sizes := memsize.Scan(val) + h.reports[id] = Report{ + ID: id, + RootName: root, + Date: start.Truncate(1 * time.Second), + Duration: time.Since(start), + Sizes: sizes, + } + h.reportID++ + return id, true +} + +func serveHTML(w http.ResponseWriter, tpl *template.Template, status int, ti *templateInfo) { + w.Header().Set("content-type", "text/html") + var buf bytes.Buffer + if err := tpl.Execute(&buf, ti); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + buf.WriteTo(w) +} diff --git a/vendor/github.com/fjl/memsize/runtimefunc.go b/vendor/github.com/fjl/memsize/runtimefunc.go new file mode 100644 index 000000000000..912a3e768d4d --- /dev/null +++ b/vendor/github.com/fjl/memsize/runtimefunc.go @@ -0,0 +1,14 @@ +package memsize + +import "unsafe" + +var _ = unsafe.Pointer(nil) + +//go:linkname stopTheWorld runtime.stopTheWorld +func stopTheWorld(reason string) + +//go:linkname startTheWorld runtime.startTheWorld +func startTheWorld() + +//go:linkname chanbuf runtime.chanbuf +func chanbuf(ch unsafe.Pointer, i uint) unsafe.Pointer diff --git a/vendor/github.com/fjl/memsize/runtimefunc.s b/vendor/github.com/fjl/memsize/runtimefunc.s new file mode 100644 index 000000000000..a091e2fa7243 --- /dev/null +++ b/vendor/github.com/fjl/memsize/runtimefunc.s @@ -0,0 +1 @@ +// This file is required to make stub function declarations work. diff --git a/vendor/github.com/fjl/memsize/type.go b/vendor/github.com/fjl/memsize/type.go new file mode 100644 index 000000000000..5d6f59e9ff2e --- /dev/null +++ b/vendor/github.com/fjl/memsize/type.go @@ -0,0 +1,119 @@ +package memsize + +import ( + "fmt" + "reflect" +) + +// address is a memory location. +// +// Code dealing with uintptr is oblivious to the zero address. +// Code dealing with address is not: it treats the zero address +// as invalid. Offsetting an invalid address doesn't do anything. +// +// This distinction is useful because there are objects that we can't +// get the pointer to. +type address uintptr + +const invalidAddr = address(0) + +func (a address) valid() bool { + return a != 0 +} + +func (a address) addOffset(off uintptr) address { + if !a.valid() { + return invalidAddr + } + return a + address(off) +} + +func (a address) String() string { + if uintptrBits == 32 { + return fmt.Sprintf("%#0.8x", uintptr(a)) + } + return fmt.Sprintf("%#0.16x", uintptr(a)) +} + +type typCache map[reflect.Type]typInfo + +type typInfo struct { + isPointer bool + needScan bool +} + +// isPointer returns true for pointer-ish values. The notion of +// pointer includes everything but plain values, i.e. slices, maps +// channels, interfaces are 'pointer', too. +func (tc *typCache) isPointer(typ reflect.Type) bool { + return tc.info(typ).isPointer +} + +// needScan reports whether a value of the type needs to be scanned +// recursively because it may contain pointers. +func (tc *typCache) needScan(typ reflect.Type) bool { + return tc.info(typ).needScan +} + +func (tc *typCache) info(typ reflect.Type) typInfo { + info, found := (*tc)[typ] + switch { + case found: + return info + case isPointer(typ): + info = typInfo{true, true} + default: + info = typInfo{false, tc.checkNeedScan(typ)} + } + (*tc)[typ] = info + return info +} + +func (tc *typCache) checkNeedScan(typ reflect.Type) bool { + switch k := typ.Kind(); k { + case reflect.Struct: + // Structs don't need scan if none of their fields need it. + for i := 0; i < typ.NumField(); i++ { + if tc.needScan(typ.Field(i).Type) { + return true + } + } + case reflect.Array: + // Arrays don't need scan if their element type doesn't. + return tc.needScan(typ.Elem()) + } + return false +} + +func isPointer(typ reflect.Type) bool { + k := typ.Kind() + switch { + case k <= reflect.Complex128: + return false + case k == reflect.Array: + return false + case k >= reflect.Chan && k <= reflect.String: + return true + case k == reflect.Struct || k == reflect.UnsafePointer: + return false + default: + unhandledKind(k) + return false + } +} + +func unhandledKind(k reflect.Kind) { + panic("unhandled kind " + k.String()) +} + +// HumanSize formats the given number of bytes as a readable string. +func HumanSize(bytes uintptr) string { + switch { + case bytes < 1024: + return fmt.Sprintf("%d B", bytes) + case bytes < 1024*1024: + return fmt.Sprintf("%.3f KB", float64(bytes)/1024) + default: + return fmt.Sprintf("%.3f MB", float64(bytes)/1024/1024) + } +} diff --git a/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go b/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go index 9b0421f0355d..838f1bee1ba7 100644 --- a/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go +++ b/vendor/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go @@ -12,7 +12,11 @@ import ( "sync" ) -const typeShift = 3 +const typeShift = 4 + +// Verify at compile-time that typeShift is large enough to cover all FileType +// values by confirming that 0 == 0. +var _ [0]struct{} = [TypeAll >> typeShift]struct{}{} type memStorageLock struct { ms *memStorage @@ -143,7 +147,7 @@ func (ms *memStorage) Remove(fd FileDesc) error { } func (ms *memStorage) Rename(oldfd, newfd FileDesc) error { - if FileDescOk(oldfd) || FileDescOk(newfd) { + if !FileDescOk(oldfd) || !FileDescOk(newfd) { return ErrInvalidFile } if oldfd == newfd { diff --git a/vendor/github.com/syndtr/goleveldb/leveldb/util.go b/vendor/github.com/syndtr/goleveldb/leveldb/util.go index e572a329e948..0e2b519e5c71 100644 --- a/vendor/github.com/syndtr/goleveldb/leveldb/util.go +++ b/vendor/github.com/syndtr/goleveldb/leveldb/util.go @@ -20,7 +20,7 @@ func shorten(str string) string { return str[:3] + ".." + str[len(str)-3:] } -var bunits = [...]string{"", "Ki", "Mi", "Gi"} +var bunits = [...]string{"", "Ki", "Mi", "Gi", "Ti"} func shortenb(bytes int) string { i := 0 diff --git a/vendor/vendor.json b/vendor/vendor.json index bf55bc437f0c..fdc77893644d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -93,10 +93,10 @@ "revisionTime": "2016-05-12T03:30:02Z" }, { - "checksumSHA1": "Fc8BCxCoQ7ZmghDT6X1cASR10Ec=", + "checksumSHA1": "jElNoLEe7m/iaoF1vYIHyNaS2SE=", "path": "github.com/elastic/gosigar", - "revision": "a3814ce5008e612a0c6d027608b54e1d0d9a5613", - "revisionTime": "2018-01-22T22:25:45Z" + "revision": "37f05ff46ffa7a825d1b24cf2b62d4a4c1a9d2e8", + "revisionTime": "2018-03-30T10:04:40Z" }, { "checksumSHA1": "qDsgp2kAeI9nhj565HUScaUyjU4=", @@ -110,6 +110,18 @@ "revision": "5ec5d9d3c2cf82e9688b34e9bc27a94d616a7193", "revisionTime": "2017-02-09T08:00:14Z" }, + { + "checksumSHA1": "Jq1rrHSGPfh689nA2hL1QVb62zE=", + "path": "github.com/fjl/memsize", + "revision": "ca190fb6ffbc076ff49197b7168a760f30182d2e", + "revisionTime": "2018-04-18T12:24:29Z" + }, + { + "checksumSHA1": "Z13QAYTqeW4cTiglkc2F05gWLu4=", + "path": "github.com/fjl/memsize/memsizeui", + "revision": "ca190fb6ffbc076ff49197b7168a760f30182d2e", + "revisionTime": "2018-04-18T12:24:29Z" + }, { "checksumSHA1": "0orwvPL96wFckVJyPl39fz2QsgA=", "path": "github.com/gizak/termui", @@ -406,76 +418,76 @@ "revisionTime": "2017-07-05T02:17:15Z" }, { - "checksumSHA1": "3QsnhPTXGytTbW3uDvQLgSo9s9M=", + "checksumSHA1": "k13cCuMJO7+KhR8ZXx5oUqDKGQA=", "path": "github.com/syndtr/goleveldb/leveldb", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "EKIow7XkgNdWvR/982ffIZxKG8Y=", "path": "github.com/syndtr/goleveldb/leveldb/cache", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "5KPgnvCPlR0ysDAqo6jApzRQ3tw=", "path": "github.com/syndtr/goleveldb/leveldb/comparer", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "1DRAxdlWzS4U0xKN/yQ/fdNN7f0=", "path": "github.com/syndtr/goleveldb/leveldb/errors", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "eqKeD6DS7eNCtxVYZEHHRKkyZrw=", "path": "github.com/syndtr/goleveldb/leveldb/filter", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "weSsccMav4BCerDpSLzh3mMxAYo=", "path": "github.com/syndtr/goleveldb/leveldb/iterator", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "gJY7bRpELtO0PJpZXgPQ2BYFJ88=", "path": "github.com/syndtr/goleveldb/leveldb/journal", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "MtYY1b2234y/MlS+djL8tXVAcQs=", "path": "github.com/syndtr/goleveldb/leveldb/memdb", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "UmQeotV+m8/FduKEfLOhjdp18rs=", "path": "github.com/syndtr/goleveldb/leveldb/opt", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { - "checksumSHA1": "QCSae2ub87f8awH+PKMpd8ZYOtg=", + "checksumSHA1": "7H3fa12T7WoMAeXq1+qG5O7LD0w=", "path": "github.com/syndtr/goleveldb/leveldb/storage", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "gWFPMz8OQeul0t54RM66yMTX49g=", "path": "github.com/syndtr/goleveldb/leveldb/table", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "V/Dh7NV0/fy/5jX1KaAjmGcNbzI=", "path": "github.com/syndtr/goleveldb/leveldb/util", - "revision": "169b1b37be738edb2813dab48c97a549bcf99bb5", - "revisionTime": "2018-03-07T11:33:52Z" + "revision": "ae970a0732be3a1f5311da86118d37b9f4bd2a5a", + "revisionTime": "2018-05-02T07:23:49Z" }, { "checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=", diff --git a/whisper/shhclient/client.go b/whisper/shhclient/client.go index bbe694baaa1d..7b25e739e159 100644 --- a/whisper/shhclient/client.go +++ b/whisper/shhclient/client.go @@ -67,7 +67,6 @@ func (sc *Client) SetMaxMessageSize(ctx context.Context, size uint32) error { } // SetMinimumPoW (experimental) sets the minimal PoW required by this node. - // This experimental function was introduced for the future dynamic adjustment of // PoW requirement. If the node is overwhelmed with messages, it should raise the // PoW requirement and notify the peers. The new value should be set relative to @@ -77,7 +76,7 @@ func (sc *Client) SetMinimumPoW(ctx context.Context, pow float64) error { return sc.c.CallContext(ctx, &ignored, "shh_setMinPoW", pow) } -// Marks specific peer trusted, which will allow it to send historic (expired) messages. +// MarkTrustedPeer marks specific peer trusted, which will allow it to send historic (expired) messages. // Note This function is not adding new nodes, the node needs to exists as a peer. func (sc *Client) MarkTrustedPeer(ctx context.Context, enode string) error { var ignored bool diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index ee566625c94d..c56d13949960 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -32,10 +32,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -const ( - filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds -) - var ( ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") ErrInvalidSymmetricKey = errors.New("invalid symmetric key") @@ -93,7 +89,7 @@ func (api *PublicWhisperAPI) SetMaxMessageSize(ctx context.Context, size uint32) return true, api.w.SetMaxMessageSize(size) } -// SetMinPow sets the minimum PoW for a message before it is accepted. +// SetMinPoW sets the minimum PoW for a message before it is accepted. func (api *PublicWhisperAPI) SetMinPoW(ctx context.Context, pow float64) (bool, error) { return true, api.w.SetMinimumPoW(pow) } @@ -146,7 +142,7 @@ func (api *PublicWhisperAPI) GetPublicKey(ctx context.Context, id string) (hexut return crypto.FromECDSAPub(&key.PublicKey), nil } -// GetPublicKey returns the private key associated with the given key. The key is the hex +// GetPrivateKey returns the private key associated with the given key. The key is the hex // encoded representation of a key in the form specified in section 4.3.6 of ANSI X9.62. func (api *PublicWhisperAPI) GetPrivateKey(ctx context.Context, id string) (hexutil.Bytes, error) { key, err := api.w.GetPrivateKey(id) diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index 7a57488bd7a3..8161db8ed64d 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -15,7 +15,7 @@ // along with the go-ethereum library. If not, see . /* -Package whisper implements the Whisper protocol (version 5). +Package whisperv5 implements the Whisper protocol (version 5). Whisper combines aspects of both DHTs and datagram messaging systems (e.g. UDP). As such it may be likened and compared to both, not dissimilar to the diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index 34ce52e64f73..35711d724bf7 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -33,7 +33,7 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// Options specifies the exact way a message should be wrapped into an Envelope. +// MessageParams specifies the exact way a message should be wrapped into an Envelope. type MessageParams struct { TTL uint32 Src *ecdsa.PrivateKey @@ -86,7 +86,7 @@ func (msg *ReceivedMessage) isAsymmetricEncryption() bool { return msg.Dst != nil } -// NewMessage creates and initializes a non-signed, non-encrypted Whisper message. +// NewSentMessage creates and initializes a non-signed, non-encrypted Whisper message. func NewSentMessage(params *MessageParams) (*sentMessage, error) { msg := sentMessage{} msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit) @@ -330,7 +330,7 @@ func (msg *ReceivedMessage) extractPadding(end int) (int, bool) { return paddingSize, true } -// Recover retrieves the public key of the message signer. +// SigToPubKey retrieves the public key of the message signer. func (msg *ReceivedMessage) SigToPubKey() *ecdsa.PublicKey { defer func() { recover() }() // in case of invalid signature diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go index 179c93179514..da076319920f 100644 --- a/whisper/whisperv5/peer.go +++ b/whisper/whisperv5/peer.go @@ -27,7 +27,7 @@ import ( set "gopkg.in/fatih/set.v0" ) -// peer represents a whisper protocol peer connection. +// Peer represents a whisper protocol peer connection. type Peer struct { host *Whisper peer *p2p.Peer @@ -53,51 +53,51 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer { // start initiates the peer updater, periodically broadcasting the whisper packets // into the network. -func (p *Peer) start() { - go p.update() - log.Trace("start", "peer", p.ID()) +func (peer *Peer) start() { + go peer.update() + log.Trace("start", "peer", peer.ID()) } // stop terminates the peer updater, stopping message forwarding to it. -func (p *Peer) stop() { - close(p.quit) - log.Trace("stop", "peer", p.ID()) +func (peer *Peer) stop() { + close(peer.quit) + log.Trace("stop", "peer", peer.ID()) } // handshake sends the protocol initiation status message to the remote peer and // verifies the remote status too. -func (p *Peer) handshake() error { +func (peer *Peer) handshake() error { // Send the handshake status message asynchronously errc := make(chan error, 1) go func() { - errc <- p2p.Send(p.ws, statusCode, ProtocolVersion) + errc <- p2p.Send(peer.ws, statusCode, ProtocolVersion) }() // Fetch the remote status packet and verify protocol match - packet, err := p.ws.ReadMsg() + packet, err := peer.ws.ReadMsg() if err != nil { return err } if packet.Code != statusCode { - return fmt.Errorf("peer [%x] sent packet %x before status packet", p.ID(), packet.Code) + return fmt.Errorf("peer [%x] sent packet %x before status packet", peer.ID(), packet.Code) } s := rlp.NewStream(packet.Payload, uint64(packet.Size)) peerVersion, err := s.Uint() if err != nil { - return fmt.Errorf("peer [%x] sent bad status message: %v", p.ID(), err) + return fmt.Errorf("peer [%x] sent bad status message: %v", peer.ID(), err) } if peerVersion != ProtocolVersion { - return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", p.ID(), peerVersion, ProtocolVersion) + return fmt.Errorf("peer [%x]: protocol version mismatch %d != %d", peer.ID(), peerVersion, ProtocolVersion) } // Wait until out own status is consumed too if err := <-errc; err != nil { - return fmt.Errorf("peer [%x] failed to send status packet: %v", p.ID(), err) + return fmt.Errorf("peer [%x] failed to send status packet: %v", peer.ID(), err) } return nil } // update executes periodic operations on the peer, including message transmission // and expiration. -func (p *Peer) update() { +func (peer *Peer) update() { // Start the tickers for the updates expire := time.NewTicker(expirationCycle) transmit := time.NewTicker(transmissionCycle) @@ -106,15 +106,15 @@ func (p *Peer) update() { for { select { case <-expire.C: - p.expire() + peer.expire() case <-transmit.C: - if err := p.broadcast(); err != nil { - log.Trace("broadcast failed", "reason", err, "peer", p.ID()) + if err := peer.broadcast(); err != nil { + log.Trace("broadcast failed", "reason", err, "peer", peer.ID()) return } - case <-p.quit: + case <-peer.quit: return } } @@ -148,16 +148,16 @@ func (peer *Peer) expire() { // broadcast iterates over the collection of envelopes and transmits yet unknown // ones over the network. -func (p *Peer) broadcast() error { +func (peer *Peer) broadcast() error { var cnt int - envelopes := p.host.Envelopes() + envelopes := peer.host.Envelopes() for _, envelope := range envelopes { - if !p.marked(envelope) { - err := p2p.Send(p.ws, messagesCode, envelope) + if !peer.marked(envelope) { + err := p2p.Send(peer.ws, messagesCode, envelope) if err != nil { return err } else { - p.mark(envelope) + peer.mark(envelope) cnt++ } } @@ -168,7 +168,7 @@ func (p *Peer) broadcast() error { return nil } -func (p *Peer) ID() []byte { - id := p.peer.ID() +func (peer *Peer) ID() []byte { + id := peer.peer.ID() return id[:] } diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index bae2adb6f564..051b52dcf891 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -32,7 +32,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" ) -var keys []string = []string{ +var keys = []string{ "d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9", "73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98", "119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc", @@ -84,9 +84,9 @@ type TestNode struct { var result TestData var nodes [NumNodes]*TestNode -var sharedKey []byte = []byte("some arbitrary data here") +var sharedKey = []byte("some arbitrary data here") var sharedTopic TopicType = TopicType{0xF, 0x1, 0x2, 0} -var expectedMessage []byte = []byte("per rectum ad astra") +var expectedMessage = []byte("per rectum ad astra") // This test does the following: // 1. creates a chain of whisper nodes, diff --git a/whisper/whisperv5/topic.go b/whisper/whisperv5/topic.go index c4ea67eefa6a..c4eda1db42f9 100644 --- a/whisper/whisperv5/topic.go +++ b/whisper/whisperv5/topic.go @@ -23,7 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -// Topic represents a cryptographically secure, probabilistic partial +// TopicType represents a cryptographically secure, probabilistic partial // classifications of a message, determined as the first (left) 4 bytes of the // SHA3 hash of some arbitrary data given by the original author of the message. type TopicType [TopicLength]byte diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index 85849ccce493..62bd1ce17915 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -469,18 +469,18 @@ func (w *Whisper) Stop() error { // HandlePeer is called by the underlying P2P layer when the whisper sub-protocol // connection is negotiated. -func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { +func (w *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { // Create the new peer and start tracking it - whisperPeer := newPeer(wh, peer, rw) + whisperPeer := newPeer(w, peer, rw) - wh.peerMu.Lock() - wh.peers[whisperPeer] = struct{}{} - wh.peerMu.Unlock() + w.peerMu.Lock() + w.peers[whisperPeer] = struct{}{} + w.peerMu.Unlock() defer func() { - wh.peerMu.Lock() - delete(wh.peers, whisperPeer) - wh.peerMu.Unlock() + w.peerMu.Lock() + delete(w.peers, whisperPeer) + w.peerMu.Unlock() }() // Run the peer handshake and state updates @@ -490,11 +490,11 @@ func (wh *Whisper) HandlePeer(peer *p2p.Peer, rw p2p.MsgReadWriter) error { whisperPeer.start() defer whisperPeer.stop() - return wh.runMessageLoop(whisperPeer, rw) + return w.runMessageLoop(whisperPeer, rw) } // runMessageLoop reads and processes inbound messages directly to merge into client-global state. -func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { +func (w *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { for { // fetch the next packet packet, err := rw.ReadMsg() @@ -502,7 +502,7 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("message loop", "peer", p.peer.ID(), "err", err) return err } - if packet.Size > wh.MaxMessageSize() { + if packet.Size > w.MaxMessageSize() { log.Warn("oversized message received", "peer", p.peer.ID()) return errors.New("oversized message received") } @@ -518,7 +518,7 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid envelope") } - cached, err := wh.add(&envelope) + cached, err := w.add(&envelope) if err != nil { log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid envelope") @@ -537,17 +537,17 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("failed to decode direct message, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid direct message") } - wh.postEvent(&envelope, true) + w.postEvent(&envelope, true) } case p2pRequestCode: // Must be processed if mail server is implemented. Otherwise ignore. - if wh.mailServer != nil { + if w.mailServer != nil { var request Envelope if err := packet.Decode(&request); err != nil { log.Warn("failed to decode p2p request message, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid p2p request") } - wh.mailServer.DeliverMail(p, &request) + w.mailServer.DeliverMail(p, &request) } default: // New message types might be implemented in the future versions of Whisper. @@ -561,29 +561,27 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { // add inserts a new envelope into the message pool to be distributed within the // whisper network. It also inserts the envelope into the expiration pool at the // appropriate time-stamp. In case of error, connection should be dropped. -func (wh *Whisper) add(envelope *Envelope) (bool, error) { +func (w *Whisper) add(envelope *Envelope) (bool, error) { now := uint32(time.Now().Unix()) sent := envelope.Expiry - envelope.TTL if sent > now { if sent-SynchAllowance > now { return false, fmt.Errorf("envelope created in the future [%x]", envelope.Hash()) - } else { - // recalculate PoW, adjusted for the time difference, plus one second for latency - envelope.calculatePoW(sent - now + 1) } + // recalculate PoW, adjusted for the time difference, plus one second for latency + envelope.calculatePoW(sent - now + 1) } if envelope.Expiry < now { if envelope.Expiry+SynchAllowance*2 < now { return false, fmt.Errorf("very old message") - } else { - log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) - return false, nil // drop envelope without error } + log.Debug("expired envelope dropped", "hash", envelope.Hash().Hex()) + return false, nil // drop envelope without error } - if uint32(envelope.size()) > wh.MaxMessageSize() { + if uint32(envelope.size()) > w.MaxMessageSize() { return false, fmt.Errorf("huge messages are not allowed [%x]", envelope.Hash()) } @@ -598,36 +596,36 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("wrong size of AESNonce: %d bytes [env: %x]", aesNonceSize, envelope.Hash()) } - if envelope.PoW() < wh.MinPow() { + if envelope.PoW() < w.MinPow() { log.Debug("envelope with low PoW dropped", "PoW", envelope.PoW(), "hash", envelope.Hash().Hex()) return false, nil // drop envelope without error } hash := envelope.Hash() - wh.poolMu.Lock() - _, alreadyCached := wh.envelopes[hash] + w.poolMu.Lock() + _, alreadyCached := w.envelopes[hash] if !alreadyCached { - wh.envelopes[hash] = envelope - if wh.expirations[envelope.Expiry] == nil { - wh.expirations[envelope.Expiry] = set.NewNonTS() + w.envelopes[hash] = envelope + if w.expirations[envelope.Expiry] == nil { + w.expirations[envelope.Expiry] = set.NewNonTS() } - if !wh.expirations[envelope.Expiry].Has(hash) { - wh.expirations[envelope.Expiry].Add(hash) + if !w.expirations[envelope.Expiry].Has(hash) { + w.expirations[envelope.Expiry].Add(hash) } } - wh.poolMu.Unlock() + w.poolMu.Unlock() if alreadyCached { log.Trace("whisper envelope already cached", "hash", envelope.Hash().Hex()) } else { log.Trace("cached whisper envelope", "hash", envelope.Hash().Hex()) - wh.statsMu.Lock() - wh.stats.memoryUsed += envelope.size() - wh.statsMu.Unlock() - wh.postEvent(envelope, false) // notify the local node about the new message - if wh.mailServer != nil { - wh.mailServer.Archive(envelope) + w.statsMu.Lock() + w.stats.memoryUsed += envelope.size() + w.statsMu.Unlock() + w.postEvent(envelope, false) // notify the local node about the new message + if w.mailServer != nil { + w.mailServer.Archive(envelope) } } return true, nil @@ -838,9 +836,8 @@ func deriveKeyMaterial(key []byte, version uint64) (derivedKey []byte, err error // because it's a once in a session experience derivedKey := pbkdf2.Key(key, nil, 65356, aesKeyLength, sha256.New) return derivedKey, nil - } else { - return nil, unknownVersionError(version) } + return nil, unknownVersionError(version) } // GenerateRandomID generates a random string, which is then returned to be used as a key id diff --git a/whisper/whisperv6/api.go b/whisper/whisperv6/api.go index 96e2b17e7cf0..c60bc46a13f2 100644 --- a/whisper/whisperv6/api.go +++ b/whisper/whisperv6/api.go @@ -32,10 +32,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -const ( - filterTimeout = 300 // filters are considered timeout out after filterTimeout seconds -) - // List of errors var ( ErrSymAsym = errors.New("specify either a symmetric or an asymmetric key") @@ -231,8 +227,9 @@ type newMessageOverride struct { Padding hexutil.Bytes } -// Post a message on the Whisper network. -func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, error) { +// Post posts a message on the Whisper network. +// returns the hash of the message in case of success. +func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (hexutil.Bytes, error) { var ( symKeyGiven = len(req.SymKeyID) > 0 pubKeyGiven = len(req.PublicKey) > 0 @@ -241,7 +238,7 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, er // user must specify either a symmetric or an asymmetric key if (symKeyGiven && pubKeyGiven) || (!symKeyGiven && !pubKeyGiven) { - return false, ErrSymAsym + return nil, ErrSymAsym } params := &MessageParams{ @@ -256,20 +253,20 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, er // Set key that is used to sign the message if len(req.Sig) > 0 { if params.Src, err = api.w.GetPrivateKey(req.Sig); err != nil { - return false, err + return nil, err } } // Set symmetric key that is used to encrypt the message if symKeyGiven { if params.Topic == (TopicType{}) { // topics are mandatory with symmetric encryption - return false, ErrNoTopics + return nil, ErrNoTopics } if params.KeySym, err = api.w.GetSymKey(req.SymKeyID); err != nil { - return false, err + return nil, err } if !validateDataIntegrity(params.KeySym, aesKeyLength) { - return false, ErrInvalidSymmetricKey + return nil, ErrInvalidSymmetricKey } } @@ -277,36 +274,47 @@ func (api *PublicWhisperAPI) Post(ctx context.Context, req NewMessage) (bool, er if pubKeyGiven { params.Dst = crypto.ToECDSAPub(req.PublicKey) if !ValidatePublicKey(params.Dst) { - return false, ErrInvalidPublicKey + return nil, ErrInvalidPublicKey } } // encrypt and sent message whisperMsg, err := NewSentMessage(params) if err != nil { - return false, err + return nil, err } + var result []byte env, err := whisperMsg.Wrap(params) if err != nil { - return false, err + return nil, err } // send to specific node (skip PoW check) if len(req.TargetPeer) > 0 { n, err := discover.ParseNode(req.TargetPeer) if err != nil { - return false, fmt.Errorf("failed to parse target peer: %s", err) + return nil, fmt.Errorf("failed to parse target peer: %s", err) + } + err = api.w.SendP2PMessage(n.ID[:], env) + if err == nil { + hash := env.Hash() + result = hash[:] } - return true, api.w.SendP2PMessage(n.ID[:], env) + return result, err } // ensure that the message PoW meets the node's minimum accepted PoW if req.PowTarget < api.w.MinPow() { - return false, ErrTooLowPoW + return nil, ErrTooLowPoW } - return true, api.w.Send(env) + err = api.w.Send(env) + if err == nil { + hash := env.Hash() + result = hash[:] + } + return result, err } //go:generate gencodec -type Criteria -field-override criteriaOverride -out gen_criteria_json.go @@ -558,9 +566,10 @@ func (api *PublicWhisperAPI) NewMessageFilter(req Criteria) (string, error) { } if len(req.Topics) > 0 { - topics = make([][]byte, 0, len(req.Topics)) - for _, topic := range req.Topics { - topics = append(topics, topic[:]) + topics = make([][]byte, len(req.Topics)) + for i, topic := range req.Topics { + topics[i] = make([]byte, TopicLength) + copy(topics[i], topic[:]) } } diff --git a/whisper/whisperv6/api_test.go b/whisper/whisperv6/api_test.go new file mode 100644 index 000000000000..004a41c9496a --- /dev/null +++ b/whisper/whisperv6/api_test.go @@ -0,0 +1,78 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package whisperv6 + +import ( + "bytes" + "crypto/ecdsa" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + set "gopkg.in/fatih/set.v0" +) + +func TestMultipleTopicCopyInNewMessageFilter(t *testing.T) { + w := &Whisper{ + privateKeys: make(map[string]*ecdsa.PrivateKey), + symKeys: make(map[string][]byte), + envelopes: make(map[common.Hash]*Envelope), + expirations: make(map[uint32]*set.SetNonTS), + peers: make(map[*Peer]struct{}), + messageQueue: make(chan *Envelope, messageQueueLimit), + p2pMsgQueue: make(chan *Envelope, messageQueueLimit), + quit: make(chan struct{}), + syncAllowance: DefaultSyncAllowance, + } + w.filters = NewFilters(w) + + keyID, err := w.GenerateSymKey() + if err != nil { + t.Fatalf("Error generating symmetric key: %v", err) + } + api := PublicWhisperAPI{ + w: w, + lastUsed: make(map[string]time.Time), + } + + t1 := [4]byte{0xde, 0xea, 0xbe, 0xef} + t2 := [4]byte{0xca, 0xfe, 0xde, 0xca} + + crit := Criteria{ + SymKeyID: keyID, + Topics: []TopicType{TopicType(t1), TopicType(t2)}, + } + + _, err = api.NewMessageFilter(crit) + if err != nil { + t.Fatalf("Error creating the filter: %v", err) + } + + found := false + candidates := w.filters.getWatchersByTopic(TopicType(t1)) + for _, f := range candidates { + if len(f.Topics) == 2 { + if bytes.Equal(f.Topics[0], t1[:]) && bytes.Equal(f.Topics[1], t2[:]) { + found = true + } + } + } + + if !found { + t.Fatalf("Could not find filter with both topics") + } +}