mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 18:02:24 +00:00
Merge branch 'master' of https://github.com/ethereum/go-ethereum
This commit is contained in:
commit
cde27a9f0b
242 changed files with 164014 additions and 2870 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -35,6 +35,7 @@ profile.cov
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
# dashboard
|
# dashboard
|
||||||
|
/dashboard/assets/flow-typed
|
||||||
/dashboard/assets/node_modules
|
/dashboard/assets/node_modules
|
||||||
/dashboard/assets/stats.json
|
/dashboard/assets/stats.json
|
||||||
/dashboard/assets/public/bundle.js
|
/dashboard/assets/public/bundle.js
|
||||||
|
|
|
||||||
28
.travis.yml
28
.travis.yml
|
|
@ -8,7 +8,6 @@ matrix:
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.7.x
|
go: 1.7.x
|
||||||
script:
|
script:
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
|
|
@ -20,7 +19,6 @@ matrix:
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.8.x
|
go: 1.8.x
|
||||||
script:
|
script:
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
|
|
@ -33,7 +31,6 @@ matrix:
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.9.x
|
go: 1.9.x
|
||||||
script:
|
script:
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
|
||||||
- sudo modprobe fuse
|
- sudo modprobe fuse
|
||||||
- sudo chmod 666 /dev/fuse
|
- sudo chmod 666 /dev/fuse
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
- sudo chown root:$USER /etc/fuse.conf
|
||||||
|
|
@ -42,7 +39,6 @@ matrix:
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
go: 1.9.x
|
go: 1.9.x
|
||||||
sudo: required
|
|
||||||
script:
|
script:
|
||||||
- brew update
|
- brew update
|
||||||
- brew install caskroom/cask/brew-cask
|
- brew install caskroom/cask/brew-cask
|
||||||
|
|
@ -53,15 +49,12 @@ matrix:
|
||||||
# This builder only tests code linters on latest version of Go
|
# This builder only tests code linters on latest version of Go
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
|
||||||
go: 1.9.x
|
go: 1.9.x
|
||||||
env:
|
env:
|
||||||
- lint
|
- lint
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
|
|
||||||
- sudo modprobe fuse
|
|
||||||
- sudo chmod 666 /dev/fuse
|
|
||||||
- sudo chown root:$USER /etc/fuse.conf
|
|
||||||
- go run build/ci.go lint
|
- go run build/ci.go lint
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA and Linux Azure uploads
|
# This builder does the Ubuntu PPA and Linux Azure uploads
|
||||||
|
|
@ -72,6 +65,8 @@ matrix:
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
- azure-linux
|
- azure-linux
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
|
@ -104,12 +99,13 @@ matrix:
|
||||||
# This builder does the Linux Azure MIPS xgo uploads
|
# This builder does the Linux Azure MIPS xgo uploads
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: 1.9.x
|
go: 1.9.x
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
|
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
|
||||||
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
|
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
|
||||||
|
|
@ -146,6 +142,8 @@ matrix:
|
||||||
env:
|
env:
|
||||||
- azure-android
|
- azure-android
|
||||||
- maven-android
|
- maven-android
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
before_install:
|
before_install:
|
||||||
- curl https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz | tar -xz
|
- curl https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz | tar -xz
|
||||||
- export PATH=`pwd`/go/bin:$PATH
|
- export PATH=`pwd`/go/bin:$PATH
|
||||||
|
|
@ -169,6 +167,8 @@ matrix:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
- azure-ios
|
- azure-ios
|
||||||
- cocoapods-ios
|
- cocoapods-ios
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
@ -193,15 +193,11 @@ matrix:
|
||||||
go: 1.9.x
|
go: 1.9.x
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go purge -store gethstore/builds -days 14
|
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||||
|
|
||||||
install:
|
|
||||||
- go get golang.org/x/tools/cmd/cover
|
|
||||||
script:
|
|
||||||
- go run build/ci.go install
|
|
||||||
- go run build/ci.go test -coverage
|
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
webhooks:
|
webhooks:
|
||||||
urls:
|
urls:
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -50,51 +51,47 @@ func JSON(reader io.Reader) (ABI, error) {
|
||||||
// methods string signature. (signature = baz(uint32,string32))
|
// methods string signature. (signature = baz(uint32,string32))
|
||||||
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
// Fetch the ABI of the requested method
|
// Fetch the ABI of the requested method
|
||||||
var method Method
|
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
method = abi.Constructor
|
// constructor
|
||||||
} else {
|
arguments, err := abi.Constructor.Inputs.Pack(args...)
|
||||||
m, exist := abi.Methods[name]
|
if err != nil {
|
||||||
if !exist {
|
return nil, err
|
||||||
return nil, fmt.Errorf("method '%s' not found", name)
|
|
||||||
}
|
}
|
||||||
method = m
|
return arguments, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
arguments, err := method.pack(args...)
|
method, exist := abi.Methods[name]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("method '%s' not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments, err := method.Inputs.Pack(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Pack up the method ID too if not a constructor and return
|
// Pack up the method ID too if not a constructor and return
|
||||||
if name == "" {
|
|
||||||
return arguments, nil
|
|
||||||
}
|
|
||||||
return append(method.Id(), arguments...), nil
|
return append(method.Id(), arguments...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack output in v according to the abi specification
|
// Unpack output in v according to the abi specification
|
||||||
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||||
if err = bytesAreProper(output); err != nil {
|
if len(output) == 0 {
|
||||||
return err
|
return fmt.Errorf("abi: unmarshalling empty output")
|
||||||
}
|
}
|
||||||
// since there can't be naming collisions with contracts and events,
|
// since there can't be naming collisions with contracts and events,
|
||||||
// we need to decide whether we're calling a method or an event
|
// we need to decide whether we're calling a method or an event
|
||||||
var unpack unpacker
|
|
||||||
if method, ok := abi.Methods[name]; ok {
|
if method, ok := abi.Methods[name]; ok {
|
||||||
unpack = method
|
if len(output)%32 != 0 {
|
||||||
|
return fmt.Errorf("abi: improperly formatted output")
|
||||||
|
}
|
||||||
|
return method.Outputs.Unpack(v, output)
|
||||||
} else if event, ok := abi.Events[name]; ok {
|
} else if event, ok := abi.Events[name]; ok {
|
||||||
unpack = event
|
return event.Inputs.Unpack(v, output)
|
||||||
} else {
|
|
||||||
return fmt.Errorf("abi: could not locate named method or event.")
|
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("abi: could not locate named method or event")
|
||||||
// requires a struct to unpack into for a tuple return...
|
|
||||||
if unpack.isTupleReturn() {
|
|
||||||
return unpack.tupleUnpack(v, output)
|
|
||||||
}
|
|
||||||
return unpack.singleUnpack(v, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
var fields []struct {
|
var fields []struct {
|
||||||
Type string
|
Type string
|
||||||
|
|
@ -137,3 +134,14 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MethodById looks up a method by the 4-byte id
|
||||||
|
// returns nil if none found
|
||||||
|
func (abi *ABI) MethodById(sigdata []byte) *Method {
|
||||||
|
for _, method := range abi.Methods {
|
||||||
|
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||||
|
return &method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,15 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
@ -74,9 +76,24 @@ func TestReader(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// deep equal fails for some reason
|
// deep equal fails for some reason
|
||||||
t.Skip()
|
for name, expM := range exp.Methods {
|
||||||
if !reflect.DeepEqual(abi, exp) {
|
gotM, exist := abi.Methods[name]
|
||||||
t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp)
|
if !exist {
|
||||||
|
t.Errorf("Missing expected method %v", name)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotM, expM) {
|
||||||
|
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, gotM := range abi.Methods {
|
||||||
|
expM, exist := exp.Methods[name]
|
||||||
|
if !exist {
|
||||||
|
t.Errorf("Found extra method %v", name)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotM, expM) {
|
||||||
|
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,6 +365,188 @@ func TestInputVariableInputLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
|
||||||
|
const definition = `[
|
||||||
|
{ "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
|
||||||
|
{ "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] },
|
||||||
|
{ "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] },
|
||||||
|
{ "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
|
||||||
|
]`
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test string, fixed array uint256[2]
|
||||||
|
strin := "hello world"
|
||||||
|
arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||||
|
fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate expected output
|
||||||
|
offset := make([]byte, 32)
|
||||||
|
offset[31] = 96
|
||||||
|
length := make([]byte, 32)
|
||||||
|
length[31] = byte(len(strin))
|
||||||
|
strvalue := common.RightPadBytes([]byte(strin), 32)
|
||||||
|
arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32)
|
||||||
|
arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32)
|
||||||
|
exp := append(offset, arrinvalue1...)
|
||||||
|
exp = append(exp, arrinvalue2...)
|
||||||
|
exp = append(exp, append(length, strvalue...)...)
|
||||||
|
|
||||||
|
// ignore first 4 bytes of the output. This is the function identifier
|
||||||
|
fixedArrStrPack = fixedArrStrPack[4:]
|
||||||
|
if !bytes.Equal(fixedArrStrPack, exp) {
|
||||||
|
t.Errorf("expected %x, got %x\n", exp, fixedArrStrPack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test byte array, fixed array uint256[2]
|
||||||
|
bytesin := []byte(strin)
|
||||||
|
arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||||
|
fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate expected output
|
||||||
|
offset = make([]byte, 32)
|
||||||
|
offset[31] = 96
|
||||||
|
length = make([]byte, 32)
|
||||||
|
length[31] = byte(len(strin))
|
||||||
|
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||||
|
arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32)
|
||||||
|
arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32)
|
||||||
|
exp = append(offset, arrinvalue1...)
|
||||||
|
exp = append(exp, arrinvalue2...)
|
||||||
|
exp = append(exp, append(length, strvalue...)...)
|
||||||
|
|
||||||
|
// ignore first 4 bytes of the output. This is the function identifier
|
||||||
|
fixedArrBytesPack = fixedArrBytesPack[4:]
|
||||||
|
if !bytes.Equal(fixedArrBytesPack, exp) {
|
||||||
|
t.Errorf("expected %x, got %x\n", exp, fixedArrBytesPack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test string, fixed array uint256[2], dynamic array uint256[]
|
||||||
|
strin = "hello world"
|
||||||
|
fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||||
|
dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||||
|
mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate expected output
|
||||||
|
stroffset := make([]byte, 32)
|
||||||
|
stroffset[31] = 128
|
||||||
|
strlength := make([]byte, 32)
|
||||||
|
strlength[31] = byte(len(strin))
|
||||||
|
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||||
|
fixedarrinvalue1 := common.LeftPadBytes(fixedarrin[0].Bytes(), 32)
|
||||||
|
fixedarrinvalue2 := common.LeftPadBytes(fixedarrin[1].Bytes(), 32)
|
||||||
|
dynarroffset := make([]byte, 32)
|
||||||
|
dynarroffset[31] = byte(160 + ((len(strin)/32)+1)*32)
|
||||||
|
dynarrlength := make([]byte, 32)
|
||||||
|
dynarrlength[31] = byte(len(dynarrin))
|
||||||
|
dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32)
|
||||||
|
dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32)
|
||||||
|
dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32)
|
||||||
|
exp = append(stroffset, fixedarrinvalue1...)
|
||||||
|
exp = append(exp, fixedarrinvalue2...)
|
||||||
|
exp = append(exp, dynarroffset...)
|
||||||
|
exp = append(exp, append(strlength, strvalue...)...)
|
||||||
|
dynarrarg := append(dynarrlength, dynarrinvalue1...)
|
||||||
|
dynarrarg = append(dynarrarg, dynarrinvalue2...)
|
||||||
|
dynarrarg = append(dynarrarg, dynarrinvalue3...)
|
||||||
|
exp = append(exp, dynarrarg...)
|
||||||
|
|
||||||
|
// ignore first 4 bytes of the output. This is the function identifier
|
||||||
|
mixedArrStrPack = mixedArrStrPack[4:]
|
||||||
|
if !bytes.Equal(mixedArrStrPack, exp) {
|
||||||
|
t.Errorf("expected %x, got %x\n", exp, mixedArrStrPack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test string, fixed array uint256[2], fixed array uint256[3]
|
||||||
|
strin = "hello world"
|
||||||
|
fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||||
|
fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||||
|
doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate expected output
|
||||||
|
stroffset = make([]byte, 32)
|
||||||
|
stroffset[31] = 192
|
||||||
|
strlength = make([]byte, 32)
|
||||||
|
strlength[31] = byte(len(strin))
|
||||||
|
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||||
|
fixedarrin1value1 := common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
|
||||||
|
fixedarrin1value2 := common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
|
||||||
|
fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
|
||||||
|
fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
|
||||||
|
fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
|
||||||
|
exp = append(stroffset, fixedarrin1value1...)
|
||||||
|
exp = append(exp, fixedarrin1value2...)
|
||||||
|
exp = append(exp, fixedarrin2value1...)
|
||||||
|
exp = append(exp, fixedarrin2value2...)
|
||||||
|
exp = append(exp, fixedarrin2value3...)
|
||||||
|
exp = append(exp, append(strlength, strvalue...)...)
|
||||||
|
|
||||||
|
// ignore first 4 bytes of the output. This is the function identifier
|
||||||
|
doubleFixedArrStrPack = doubleFixedArrStrPack[4:]
|
||||||
|
if !bytes.Equal(doubleFixedArrStrPack, exp) {
|
||||||
|
t.Errorf("expected %x, got %x\n", exp, doubleFixedArrStrPack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test string, fixed array uint256[2], dynamic array uint256[], fixed array uint256[3]
|
||||||
|
strin = "hello world"
|
||||||
|
fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||||
|
dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)}
|
||||||
|
fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
|
||||||
|
multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate expected output
|
||||||
|
stroffset = make([]byte, 32)
|
||||||
|
stroffset[31] = 224
|
||||||
|
strlength = make([]byte, 32)
|
||||||
|
strlength[31] = byte(len(strin))
|
||||||
|
strvalue = common.RightPadBytes([]byte(strin), 32)
|
||||||
|
fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
|
||||||
|
fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
|
||||||
|
dynarroffset = U256(big.NewInt(int64(256 + ((len(strin)/32)+1)*32)))
|
||||||
|
dynarrlength = make([]byte, 32)
|
||||||
|
dynarrlength[31] = byte(len(dynarrin))
|
||||||
|
dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32)
|
||||||
|
dynarrinvalue2 = common.LeftPadBytes(dynarrin[1].Bytes(), 32)
|
||||||
|
fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
|
||||||
|
fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
|
||||||
|
fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
|
||||||
|
exp = append(stroffset, fixedarrin1value1...)
|
||||||
|
exp = append(exp, fixedarrin1value2...)
|
||||||
|
exp = append(exp, dynarroffset...)
|
||||||
|
exp = append(exp, fixedarrin2value1...)
|
||||||
|
exp = append(exp, fixedarrin2value2...)
|
||||||
|
exp = append(exp, fixedarrin2value3...)
|
||||||
|
exp = append(exp, append(strlength, strvalue...)...)
|
||||||
|
dynarrarg = append(dynarrlength, dynarrinvalue1...)
|
||||||
|
dynarrarg = append(dynarrarg, dynarrinvalue2...)
|
||||||
|
exp = append(exp, dynarrarg...)
|
||||||
|
|
||||||
|
// ignore first 4 bytes of the output. This is the function identifier
|
||||||
|
multipleMixedArrStrPack = multipleMixedArrStrPack[4:]
|
||||||
|
if !bytes.Equal(multipleMixedArrStrPack, exp) {
|
||||||
|
t.Errorf("expected %x, got %x\n", exp, multipleMixedArrStrPack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultFunctionParsing(t *testing.T) {
|
func TestDefaultFunctionParsing(t *testing.T) {
|
||||||
const definition = `[{ "name" : "balance" }]`
|
const definition = `[{ "name" : "balance" }]`
|
||||||
|
|
||||||
|
|
@ -418,3 +617,82 @@ func TestBareEvents(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestUnpackEvent is based on this contract:
|
||||||
|
// contract T {
|
||||||
|
// event received(address sender, uint amount, bytes memo);
|
||||||
|
// function receive(bytes memo) external payable {
|
||||||
|
// received(msg.sender, msg.value, memo);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
|
||||||
|
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
|
||||||
|
func TestUnpackEvent(t *testing.T) {
|
||||||
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||||
|
data, err := hex.DecodeString(hexdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceivedEvent struct {
|
||||||
|
Address common.Address
|
||||||
|
Amount *big.Int
|
||||||
|
Memo []byte
|
||||||
|
}
|
||||||
|
var ev ReceivedEvent
|
||||||
|
|
||||||
|
err = abi.Unpack(&ev, "received", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else {
|
||||||
|
t.Logf("len(data): %d; received event: %+v", len(data), ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABI_MethodById(t *testing.T) {
|
||||||
|
const abiJSON = `[
|
||||||
|
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
|
||||||
|
{"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]},
|
||||||
|
{"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]},
|
||||||
|
{"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]},
|
||||||
|
{"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]},
|
||||||
|
{"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]},
|
||||||
|
{"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]},
|
||||||
|
{"type":"function","name":"balance","constant":true},
|
||||||
|
{"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]},
|
||||||
|
{"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]},
|
||||||
|
{"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]},
|
||||||
|
{"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]},
|
||||||
|
{"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]},
|
||||||
|
{"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]},
|
||||||
|
{"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]},
|
||||||
|
{"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]},
|
||||||
|
{"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]},
|
||||||
|
{"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]},
|
||||||
|
{"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]},
|
||||||
|
{"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]},
|
||||||
|
{"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]}
|
||||||
|
]
|
||||||
|
`
|
||||||
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for name, m := range abi.Methods {
|
||||||
|
a := fmt.Sprintf("%v", m)
|
||||||
|
b := fmt.Sprintf("%v", abi.MethodById(m.Id()))
|
||||||
|
if a != b {
|
||||||
|
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ package abi
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Argument holds the name of the argument and the corresponding type.
|
// Argument holds the name of the argument and the corresponding type.
|
||||||
|
|
@ -29,7 +31,10 @@ type Argument struct {
|
||||||
Indexed bool // indexed is only used by events
|
Indexed bool // indexed is only used by events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Argument) UnmarshalJSON(data []byte) error {
|
type Arguments []Argument
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
|
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
||||||
var extarg struct {
|
var extarg struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
|
|
@ -40,12 +45,180 @@ func (a *Argument) UnmarshalJSON(data []byte) error {
|
||||||
return fmt.Errorf("argument json err: %v", err)
|
return fmt.Errorf("argument json err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Type, err = NewType(extarg.Type)
|
argument.Type, err = NewType(extarg.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.Name = extarg.Name
|
argument.Name = extarg.Name
|
||||||
a.Indexed = extarg.Indexed
|
argument.Indexed = extarg.Indexed
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
|
||||||
|
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
|
||||||
|
func (arguments Arguments) LengthNonIndexed() int {
|
||||||
|
out := 0
|
||||||
|
for _, arg := range arguments {
|
||||||
|
if !arg.Indexed {
|
||||||
|
out++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
||||||
|
func (arguments Arguments) isTuple() bool {
|
||||||
|
return len(arguments) > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack performs the operation hexdata -> Go format
|
||||||
|
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
||||||
|
if arguments.isTuple() {
|
||||||
|
return arguments.unpackTuple(v, data)
|
||||||
|
}
|
||||||
|
return arguments.unpackAtomic(v, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is arguments pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
value = valueOf.Elem()
|
||||||
|
typ = value.Type()
|
||||||
|
kind = value.Kind()
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// `i` counts the nonindexed arguments.
|
||||||
|
// `j` counts the number of complex types.
|
||||||
|
// both `i` and `j` are used to to correctly compute `data` offset.
|
||||||
|
|
||||||
|
i, j := -1, 0
|
||||||
|
for _, arg := range arguments {
|
||||||
|
|
||||||
|
if arg.Indexed {
|
||||||
|
// can't read, continue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg.Type.T == ArrayTy {
|
||||||
|
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
|
||||||
|
// we need to decrement 'j' because 'i' was incremented
|
||||||
|
j += arg.Type.Size - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectValue := reflect.ValueOf(marshalledValue)
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Struct:
|
||||||
|
for j := 0; j < typ.NumField(); j++ {
|
||||||
|
field := typ.Field(j)
|
||||||
|
// TODO read tags: `abi:"fieldName"`
|
||||||
|
if field.Name == strings.ToUpper(arg.Name[:1])+arg.Name[1:] {
|
||||||
|
if err := set(value.Field(j), reflectValue, arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if value.Len() < i {
|
||||||
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||||
|
}
|
||||||
|
v := value.Index(i)
|
||||||
|
if err := requireAssignable(v, reflectValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := set(v.Elem(), reflectValue, arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
||||||
|
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is arguments pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
arg := arguments[0]
|
||||||
|
if arg.Indexed {
|
||||||
|
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
||||||
|
}
|
||||||
|
|
||||||
|
value := valueOf.Elem()
|
||||||
|
|
||||||
|
marshalledValue, err := toGoType(0, arg.Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return set(value, reflect.ValueOf(marshalledValue), arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack performs the operation Go format -> Hexdata
|
||||||
|
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
|
// Make sure arguments match up and pack them
|
||||||
|
abiArgs := arguments
|
||||||
|
if len(args) != len(abiArgs) {
|
||||||
|
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable input is the output appended at the end of packed
|
||||||
|
// output. This is used for strings and bytes types input.
|
||||||
|
var variableInput []byte
|
||||||
|
|
||||||
|
// input offset is the bytes offset for packed output
|
||||||
|
inputOffset := 0
|
||||||
|
for _, abiArg := range abiArgs {
|
||||||
|
if abiArg.Type.T == ArrayTy {
|
||||||
|
inputOffset += (32 * abiArg.Type.Size)
|
||||||
|
} else {
|
||||||
|
inputOffset += 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []byte
|
||||||
|
for i, a := range args {
|
||||||
|
input := abiArgs[i]
|
||||||
|
// pack the input
|
||||||
|
packed, err := input.Type.pack(reflect.ValueOf(a))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for a slice type (string, bytes, slice)
|
||||||
|
if input.Type.requiresLengthPrefix() {
|
||||||
|
// calculate the offset
|
||||||
|
offset := inputOffset + len(variableInput)
|
||||||
|
// set the offset
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
// Append the packed output to the variable input. The variable input
|
||||||
|
// will be appended at the end of the input.
|
||||||
|
variableInput = append(variableInput, packed...)
|
||||||
|
} else {
|
||||||
|
// append the packed value to the input
|
||||||
|
ret = append(ret, packed...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// append the variable input at the end of the packed input
|
||||||
|
ret = append(ret, variableInput...)
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func (b *SimulatedBackend) Rollback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SimulatedBackend) rollback() {
|
func (b *SimulatedBackend) rollback() {
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +291,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
|
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
for _, tx := range b.pendingBlock.Transactions() {
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +306,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
for _, tx := range b.pendingBlock.Transactions() {
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
return string(code), nil
|
return string(code), nil
|
||||||
}
|
}
|
||||||
// For all others just return as is for now
|
// For all others just return as is for now
|
||||||
return string(buffer.Bytes()), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindType is a set of type binders that convert Solidity types to some supported
|
// bindType is a set of type binders that convert Solidity types to some supported
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -31,7 +30,7 @@ import (
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Name string
|
Name string
|
||||||
Anonymous bool
|
Anonymous bool
|
||||||
Inputs []Argument
|
Inputs Arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id returns the canonical representation of the event's signature used by the
|
// Id returns the canonical representation of the event's signature used by the
|
||||||
|
|
@ -45,93 +44,3 @@ func (e Event) Id() common.Hash {
|
||||||
}
|
}
|
||||||
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
|
||||||
}
|
}
|
||||||
|
|
||||||
// unpacks an event return tuple into a struct of corresponding go types
|
|
||||||
//
|
|
||||||
// Unpacking can be done into a struct or a slice/array.
|
|
||||||
func (e Event) tupleUnpack(v interface{}, output []byte) error {
|
|
||||||
// make sure the passed value is a pointer
|
|
||||||
valueOf := reflect.ValueOf(v)
|
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
value = valueOf.Elem()
|
|
||||||
typ = value.Type()
|
|
||||||
)
|
|
||||||
|
|
||||||
if value.Kind() != reflect.Struct {
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < len(e.Inputs); i++ {
|
|
||||||
input := e.Inputs[i]
|
|
||||||
if input.Indexed {
|
|
||||||
// can't read, continue
|
|
||||||
continue
|
|
||||||
} else if input.Type.T == ArrayTy {
|
|
||||||
// need to move this up because they read sequentially
|
|
||||||
j += input.Type.Size
|
|
||||||
}
|
|
||||||
marshalledValue, err := toGoType((i+j)*32, input.Type, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
|
||||||
|
|
||||||
switch value.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
for j := 0; j < typ.NumField(); j++ {
|
|
||||||
field := typ.Field(j)
|
|
||||||
// TODO read tags: `abi:"fieldName"`
|
|
||||||
if field.Name == strings.ToUpper(e.Inputs[i].Name[:1])+e.Inputs[i].Name[1:] {
|
|
||||||
if err := set(value.Field(j), reflectValue, e.Inputs[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
if value.Len() < i {
|
|
||||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(e.Inputs), value.Len())
|
|
||||||
}
|
|
||||||
v := value.Index(i)
|
|
||||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
|
|
||||||
}
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
|
||||||
if err := set(v.Elem(), reflectValue, e.Inputs[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Event) isTupleReturn() bool { return len(e.Inputs) > 1 }
|
|
||||||
|
|
||||||
func (e Event) singleUnpack(v interface{}, output []byte) error {
|
|
||||||
// make sure the passed value is a pointer
|
|
||||||
valueOf := reflect.ValueOf(v)
|
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Inputs[0].Indexed {
|
|
||||||
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
|
||||||
}
|
|
||||||
|
|
||||||
value := valueOf.Elem()
|
|
||||||
|
|
||||||
marshalledValue, err := toGoType(0, e.Inputs[0].Type, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := set(value, reflect.ValueOf(marshalledValue), e.Inputs[0]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,53 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var jsonEventTransfer = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true, "name": "from", "type": "address"
|
||||||
|
}, {
|
||||||
|
"indexed": true, "name": "to", "type": "address"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "value", "type": "uint256"
|
||||||
|
}],
|
||||||
|
"name": "Transfer",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var jsonEventPledge = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{
|
||||||
|
"indexed": false, "name": "who", "type": "address"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "wad", "type": "uint128"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "currency", "type": "bytes3"
|
||||||
|
}],
|
||||||
|
"name": "Pledge",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// 1000000
|
||||||
|
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||||
|
|
||||||
|
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||||
|
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
func TestEventId(t *testing.T) {
|
func TestEventId(t *testing.T) {
|
||||||
var table = []struct {
|
var table = []struct {
|
||||||
definition string
|
definition string
|
||||||
|
|
@ -54,3 +94,223 @@ func TestEventId(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||||
|
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
|
type testStruct struct {
|
||||||
|
Value1 [2]uint8
|
||||||
|
Value2 uint8
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var b bytes.Buffer
|
||||||
|
var i uint8 = 1
|
||||||
|
for ; i <= 3; i++ {
|
||||||
|
b.Write(packNum(reflect.ValueOf(i)))
|
||||||
|
}
|
||||||
|
var rst testStruct
|
||||||
|
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||||
|
require.Equal(t, [2]uint8{1, 2}, rst.Value1)
|
||||||
|
require.Equal(t, uint8(3), rst.Value2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventTupleUnpack(t *testing.T) {
|
||||||
|
|
||||||
|
type EventTransfer struct {
|
||||||
|
Value *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventPledge struct {
|
||||||
|
Who common.Address
|
||||||
|
Wad *big.Int
|
||||||
|
Currency [3]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventPledge struct {
|
||||||
|
Who string
|
||||||
|
Wad int
|
||||||
|
Currency [3]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
bigint := new(big.Int)
|
||||||
|
bigintExpected := big.NewInt(1000000)
|
||||||
|
bigintExpected2 := big.NewInt(2218516807680)
|
||||||
|
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||||
|
var testCases = []struct {
|
||||||
|
data string
|
||||||
|
dest interface{}
|
||||||
|
expected interface{}
|
||||||
|
jsonLog []byte
|
||||||
|
error string
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
transferData1,
|
||||||
|
&EventTransfer{},
|
||||||
|
&EventTransfer{Value: bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into structure",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&[]interface{}{&bigint},
|
||||||
|
&[]interface{}{&bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into slice",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&EventPledge{},
|
||||||
|
&EventPledge{
|
||||||
|
addr,
|
||||||
|
bigintExpected2,
|
||||||
|
[3]byte{'u', 's', 'd'}},
|
||||||
|
jsonEventPledge,
|
||||||
|
"",
|
||||||
|
"Can unpack Pledge event into structure",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||||
|
&[]interface{}{
|
||||||
|
&addr,
|
||||||
|
&bigintExpected2,
|
||||||
|
&[3]byte{'u', 's', 'd'}},
|
||||||
|
jsonEventPledge,
|
||||||
|
"",
|
||||||
|
"Can unpack Pledge event into slice",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[3]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||||
|
&[3]interface{}{
|
||||||
|
&addr,
|
||||||
|
&bigintExpected2,
|
||||||
|
&[3]byte{'u', 's', 'd'}},
|
||||||
|
jsonEventPledge,
|
||||||
|
"",
|
||||||
|
"Can unpack Pledge event into an array",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[]interface{}{new(int), 0, 0},
|
||||||
|
&[]interface{}{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: cannot unmarshal common.Address in to int",
|
||||||
|
"Can not unpack Pledge event into slice with wrong types",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&BadEventPledge{},
|
||||||
|
&BadEventPledge{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: cannot unmarshal common.Address in to string",
|
||||||
|
"Can not unpack Pledge event into struct with wrong filed types",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[]interface{}{common.Address{}, new(big.Int)},
|
||||||
|
&[]interface{}{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: insufficient number of elements in the list/array for unpack, want 3, got 2",
|
||||||
|
"Can not unpack Pledge event into too short slice",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
new(map[string]interface{}),
|
||||||
|
&[]interface{}{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: cannot unmarshal tuple into map[string]interface {}",
|
||||||
|
"Can not unpack Pledge event into map",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
assert := assert.New(t)
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
||||||
|
if tc.error == "" {
|
||||||
|
assert.Nil(err, "Should be able to unpack event data.")
|
||||||
|
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(err, tc.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error {
|
||||||
|
data, err := hex.DecodeString(hexData)
|
||||||
|
assert.NoError(err, "Hex data should be a correct hex-string")
|
||||||
|
var e Event
|
||||||
|
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
|
||||||
|
a := ABI{Events: map[string]Event{"e": e}}
|
||||||
|
return a.Unpack(dest, "e", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Taken from
|
||||||
|
https://github.com/ethereum/go-ethereum/pull/15568
|
||||||
|
*/
|
||||||
|
|
||||||
|
type testResult struct {
|
||||||
|
Values [2]*big.Int
|
||||||
|
Value1 *big.Int
|
||||||
|
Value2 *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
definition string
|
||||||
|
want testResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc testCase) encoded(intType, arrayType Type) []byte {
|
||||||
|
var b bytes.Buffer
|
||||||
|
if tc.want.Value1 != nil {
|
||||||
|
val, _ := intType.pack(reflect.ValueOf(tc.want.Value1))
|
||||||
|
b.Write(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.want.Values, [2]*big.Int{nil, nil}) {
|
||||||
|
val, _ := arrayType.pack(reflect.ValueOf(tc.want.Values))
|
||||||
|
b.Write(val)
|
||||||
|
}
|
||||||
|
if tc.want.Value2 != nil {
|
||||||
|
val, _ := intType.pack(reflect.ValueOf(tc.want.Value2))
|
||||||
|
b.Write(val)
|
||||||
|
}
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
||||||
|
func TestEventUnpackIndexed(t *testing.T) {
|
||||||
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
|
type testStruct struct {
|
||||||
|
Value1 uint8
|
||||||
|
Value2 uint8
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.Write(packNum(reflect.ValueOf(uint8(8))))
|
||||||
|
var rst testStruct
|
||||||
|
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||||
|
require.Equal(t, uint8(0), rst.Value1)
|
||||||
|
require.Equal(t, uint8(8), rst.Value2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input.
|
||||||
|
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||||
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
||||||
|
type testStruct struct {
|
||||||
|
Value1 [2]uint8
|
||||||
|
Value2 string
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var b bytes.Buffer
|
||||||
|
stringOut := "abc"
|
||||||
|
// number of fields that will be encoded * 32
|
||||||
|
b.Write(packNum(reflect.ValueOf(32)))
|
||||||
|
b.Write(packNum(reflect.ValueOf(len(stringOut))))
|
||||||
|
b.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||||
|
|
||||||
|
var rst testStruct
|
||||||
|
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||||
|
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
|
||||||
|
require.Equal(t, stringOut, rst.Value2)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,12 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Callable method given a `Name` and whether the method is a constant.
|
// Method represents a callable given a `Name` and whether the method is a constant.
|
||||||
// If the method is `Const` no transaction needs to be created for this
|
// If the method is `Const` no transaction needs to be created for this
|
||||||
// particular Method call. It can easily be simulated using a local VM.
|
// particular Method call. It can easily be simulated using a local VM.
|
||||||
// For example a `Balance()` method only needs to retrieve something
|
// For example a `Balance()` method only needs to retrieve something
|
||||||
|
|
@ -35,125 +34,8 @@ import (
|
||||||
type Method struct {
|
type Method struct {
|
||||||
Name string
|
Name string
|
||||||
Const bool
|
Const bool
|
||||||
Inputs []Argument
|
Inputs Arguments
|
||||||
Outputs []Argument
|
Outputs Arguments
|
||||||
}
|
|
||||||
|
|
||||||
func (method Method) pack(args ...interface{}) ([]byte, error) {
|
|
||||||
// Make sure arguments match up and pack them
|
|
||||||
if len(args) != len(method.Inputs) {
|
|
||||||
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
|
|
||||||
}
|
|
||||||
// variable input is the output appended at the end of packed
|
|
||||||
// output. This is used for strings and bytes types input.
|
|
||||||
var variableInput []byte
|
|
||||||
|
|
||||||
var ret []byte
|
|
||||||
for i, a := range args {
|
|
||||||
input := method.Inputs[i]
|
|
||||||
// pack the input
|
|
||||||
packed, err := input.Type.pack(reflect.ValueOf(a))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("`%s` %v", method.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for a slice type (string, bytes, slice)
|
|
||||||
if input.Type.requiresLengthPrefix() {
|
|
||||||
// calculate the offset
|
|
||||||
offset := len(method.Inputs)*32 + len(variableInput)
|
|
||||||
// set the offset
|
|
||||||
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
|
||||||
// Append the packed output to the variable input. The variable input
|
|
||||||
// will be appended at the end of the input.
|
|
||||||
variableInput = append(variableInput, packed...)
|
|
||||||
} else {
|
|
||||||
// append the packed value to the input
|
|
||||||
ret = append(ret, packed...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// append the variable input at the end of the packed input
|
|
||||||
ret = append(ret, variableInput...)
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpacks a method return tuple into a struct of corresponding go types
|
|
||||||
//
|
|
||||||
// Unpacking can be done into a struct or a slice/array.
|
|
||||||
func (method Method) tupleUnpack(v interface{}, output []byte) error {
|
|
||||||
// make sure the passed value is a pointer
|
|
||||||
valueOf := reflect.ValueOf(v)
|
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
value = valueOf.Elem()
|
|
||||||
typ = value.Type()
|
|
||||||
)
|
|
||||||
|
|
||||||
j := 0
|
|
||||||
for i := 0; i < len(method.Outputs); i++ {
|
|
||||||
toUnpack := method.Outputs[i]
|
|
||||||
if toUnpack.Type.T == ArrayTy {
|
|
||||||
// need to move this up because they read sequentially
|
|
||||||
j += toUnpack.Type.Size
|
|
||||||
}
|
|
||||||
marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
|
||||||
|
|
||||||
switch value.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
for j := 0; j < typ.NumField(); j++ {
|
|
||||||
field := typ.Field(j)
|
|
||||||
// TODO read tags: `abi:"fieldName"`
|
|
||||||
if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
|
|
||||||
if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
if value.Len() < i {
|
|
||||||
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(method.Outputs), value.Len())
|
|
||||||
}
|
|
||||||
v := value.Index(i)
|
|
||||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
|
|
||||||
}
|
|
||||||
reflectValue := reflect.ValueOf(marshalledValue)
|
|
||||||
if err := set(v.Elem(), reflectValue, method.Outputs[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 }
|
|
||||||
|
|
||||||
func (method Method) singleUnpack(v interface{}, output []byte) error {
|
|
||||||
// make sure the passed value is a pointer
|
|
||||||
valueOf := reflect.ValueOf(v)
|
|
||||||
if reflect.Ptr != valueOf.Kind() {
|
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
value := valueOf.Elem()
|
|
||||||
|
|
||||||
marshalledValue, err := toGoType(0, method.Outputs[0].Type, output)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sig returns the methods string signature according to the ABI spec.
|
// Sig returns the methods string signature according to the ABI spec.
|
||||||
|
|
@ -163,35 +45,35 @@ func (method Method) singleUnpack(v interface{}, output []byte) error {
|
||||||
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
//
|
//
|
||||||
// Please note that "int" is substitute for its canonical representation "int256"
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
func (m Method) Sig() string {
|
func (method Method) Sig() string {
|
||||||
types := make([]string, len(m.Inputs))
|
types := make([]string, len(method.Inputs))
|
||||||
i := 0
|
i := 0
|
||||||
for _, input := range m.Inputs {
|
for _, input := range method.Inputs {
|
||||||
types[i] = input.Type.String()
|
types[i] = input.Type.String()
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ","))
|
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Method) String() string {
|
func (method Method) String() string {
|
||||||
inputs := make([]string, len(m.Inputs))
|
inputs := make([]string, len(method.Inputs))
|
||||||
for i, input := range m.Inputs {
|
for i, input := range method.Inputs {
|
||||||
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
|
||||||
}
|
}
|
||||||
outputs := make([]string, len(m.Outputs))
|
outputs := make([]string, len(method.Outputs))
|
||||||
for i, output := range m.Outputs {
|
for i, output := range method.Outputs {
|
||||||
if len(output.Name) > 0 {
|
if len(output.Name) > 0 {
|
||||||
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
outputs[i] = fmt.Sprintf("%v ", output.Name)
|
||||||
}
|
}
|
||||||
outputs[i] += output.Type.String()
|
outputs[i] += output.Type.String()
|
||||||
}
|
}
|
||||||
constant := ""
|
constant := ""
|
||||||
if m.Const {
|
if method.Const {
|
||||||
constant = "constant "
|
constant = "constant "
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("function %v(%v) %sreturns(%v)", m.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Method) Id() []byte {
|
func (method Method) Id() []byte {
|
||||||
return crypto.Keccak256([]byte(m.Sig()))[:4]
|
return crypto.Keccak256([]byte(method.Sig()))[:4]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,8 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
if reflectValue.Bool() {
|
if reflectValue.Bool() {
|
||||||
return math.PaddedBigBytes(common.Big1, 32)
|
return math.PaddedBigBytes(common.Big1, 32)
|
||||||
} else {
|
|
||||||
return math.PaddedBigBytes(common.Big0, 32)
|
|
||||||
}
|
}
|
||||||
|
return math.PaddedBigBytes(common.Big0, 32)
|
||||||
case BytesTy:
|
case BytesTy:
|
||||||
if reflectValue.Kind() == reflect.Array {
|
if reflectValue.Kind() == reflect.Array {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,28 @@ func set(dst, src reflect.Value, output Argument) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requireAssignable assures that `dest` is a pointer and it's not an interface.
|
||||||
|
func requireAssignable(dst, src reflect.Value) error {
|
||||||
|
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// requireUnpackKind verifies preconditions for unpacking `args` into `kind`
|
||||||
|
func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
|
||||||
|
args Arguments) error {
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Struct:
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if minLen := args.LengthNonIndexed(); v.Len() < minLen {
|
||||||
|
return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d",
|
||||||
|
minLen, v.Len())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Type enumerator
|
||||||
const (
|
const (
|
||||||
IntTy byte = iota
|
IntTy byte = iota
|
||||||
UintTy
|
UintTy
|
||||||
|
|
@ -100,69 +101,66 @@ func NewType(t string) (typ Type, err error) {
|
||||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
return typ, err
|
return typ, err
|
||||||
} else {
|
}
|
||||||
// parse the type and size of the abi-type.
|
// parse the type and size of the abi-type.
|
||||||
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||||
// varSize is the size of the variable
|
// varSize is the size of the variable
|
||||||
var varSize int
|
var varSize int
|
||||||
if len(parsedType[3]) > 0 {
|
if len(parsedType[3]) > 0 {
|
||||||
var err error
|
var err error
|
||||||
varSize, err = strconv.Atoi(parsedType[2])
|
varSize, err = strconv.Atoi(parsedType[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
|
||||||
// this should fail because it means that there's something wrong with
|
|
||||||
// the abi type (the compiler should always format it to the size...always)
|
|
||||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// varType is the parsed abi type
|
} else {
|
||||||
varType := parsedType[1]
|
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||||
|
// this should fail because it means that there's something wrong with
|
||||||
switch varType {
|
// the abi type (the compiler should always format it to the size...always)
|
||||||
case "int":
|
|
||||||
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
|
||||||
typ.Size = varSize
|
|
||||||
typ.T = IntTy
|
|
||||||
case "uint":
|
|
||||||
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
|
||||||
typ.Size = varSize
|
|
||||||
typ.T = UintTy
|
|
||||||
case "bool":
|
|
||||||
typ.Kind = reflect.Bool
|
|
||||||
typ.T = BoolTy
|
|
||||||
typ.Type = reflect.TypeOf(bool(false))
|
|
||||||
case "address":
|
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Type = address_t
|
|
||||||
typ.Size = 20
|
|
||||||
typ.T = AddressTy
|
|
||||||
case "string":
|
|
||||||
typ.Kind = reflect.String
|
|
||||||
typ.Type = reflect.TypeOf("")
|
|
||||||
typ.T = StringTy
|
|
||||||
case "bytes":
|
|
||||||
if varSize == 0 {
|
|
||||||
typ.T = BytesTy
|
|
||||||
typ.Kind = reflect.Slice
|
|
||||||
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
|
||||||
} else {
|
|
||||||
typ.T = FixedBytesTy
|
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Size = varSize
|
|
||||||
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
|
||||||
}
|
|
||||||
case "function":
|
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.T = FunctionTy
|
|
||||||
typ.Size = 24
|
|
||||||
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
|
||||||
default:
|
|
||||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// varType is the parsed abi type
|
||||||
|
switch varType := parsedType[1]; varType {
|
||||||
|
case "int":
|
||||||
|
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.T = IntTy
|
||||||
|
case "uint":
|
||||||
|
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.T = UintTy
|
||||||
|
case "bool":
|
||||||
|
typ.Kind = reflect.Bool
|
||||||
|
typ.T = BoolTy
|
||||||
|
typ.Type = reflect.TypeOf(bool(false))
|
||||||
|
case "address":
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Type = address_t
|
||||||
|
typ.Size = 20
|
||||||
|
typ.T = AddressTy
|
||||||
|
case "string":
|
||||||
|
typ.Kind = reflect.String
|
||||||
|
typ.Type = reflect.TypeOf("")
|
||||||
|
typ.T = StringTy
|
||||||
|
case "bytes":
|
||||||
|
if varSize == 0 {
|
||||||
|
typ.T = BytesTy
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||||
|
} else {
|
||||||
|
typ.T = FixedBytesTy
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||||
|
}
|
||||||
|
case "function":
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.T = FunctionTy
|
||||||
|
typ.Size = 24
|
||||||
|
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
||||||
|
default:
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
// unpacker is a utility interface that enables us to have
|
|
||||||
// abstraction between events and methods and also to properly
|
|
||||||
// "unpack" them; e.g. events use Inputs, methods use Outputs.
|
|
||||||
type unpacker interface {
|
|
||||||
tupleUnpack(v interface{}, output []byte) error
|
|
||||||
singleUnpack(v interface{}, output []byte) error
|
|
||||||
isTupleReturn() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// reads the integer based on its kind
|
// reads the integer based on its kind
|
||||||
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
func readInteger(kind reflect.Kind, b []byte) interface{} {
|
||||||
switch kind {
|
switch kind {
|
||||||
|
|
@ -79,7 +70,7 @@ func readBool(word []byte) (bool, error) {
|
||||||
// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
|
||||||
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
if t.T != FunctionTy {
|
if t.T != FunctionTy {
|
||||||
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array.")
|
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
|
||||||
}
|
}
|
||||||
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
|
||||||
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
|
||||||
|
|
@ -92,7 +83,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
|
||||||
// through reflection, creates a fixed array to be read from
|
// through reflection, creates a fixed array to be read from
|
||||||
func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
func readFixedBytes(t Type, word []byte) (interface{}, error) {
|
||||||
if t.T != FixedBytesTy {
|
if t.T != FixedBytesTy {
|
||||||
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array.")
|
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
|
||||||
}
|
}
|
||||||
// convert
|
// convert
|
||||||
array := reflect.New(t.Type).Elem()
|
array := reflect.New(t.Type).Elem()
|
||||||
|
|
@ -203,14 +194,3 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
|
||||||
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
|
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks for proper formatting of byte output
|
|
||||||
func bytesAreProper(output []byte) error {
|
|
||||||
if len(output) == 0 {
|
|
||||||
return fmt.Errorf("abi: unmarshalling empty output")
|
|
||||||
} else if len(output)%32 != 0 {
|
|
||||||
return fmt.Errorf("abi: improperly formatted output")
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type unpackTest struct {
|
type unpackTest struct {
|
||||||
|
|
@ -261,78 +263,130 @@ var unpackTests = []unpackTest{
|
||||||
|
|
||||||
func TestUnpack(t *testing.T) {
|
func TestUnpack(t *testing.T) {
|
||||||
for i, test := range unpackTests {
|
for i, test := range unpackTests {
|
||||||
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||||
abi, err := JSON(strings.NewReader(def))
|
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
|
||||||
if err != nil {
|
abi, err := JSON(strings.NewReader(def))
|
||||||
t.Fatalf("invalid ABI definition %s: %v", def, err)
|
if err != nil {
|
||||||
}
|
t.Fatalf("invalid ABI definition %s: %v", def, err)
|
||||||
encb, err := hex.DecodeString(test.enc)
|
}
|
||||||
if err != nil {
|
encb, err := hex.DecodeString(test.enc)
|
||||||
t.Fatalf("invalid hex: %s" + test.enc)
|
if err != nil {
|
||||||
}
|
t.Fatalf("invalid hex: %s" + test.enc)
|
||||||
outptr := reflect.New(reflect.TypeOf(test.want))
|
}
|
||||||
err = abi.Unpack(outptr.Interface(), "method", encb)
|
outptr := reflect.New(reflect.TypeOf(test.want))
|
||||||
if err := test.checkError(err); err != nil {
|
err = abi.Unpack(outptr.Interface(), "method", encb)
|
||||||
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
|
if err := test.checkError(err); err != nil {
|
||||||
continue
|
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
|
||||||
}
|
return
|
||||||
out := outptr.Elem().Interface()
|
}
|
||||||
if !reflect.DeepEqual(test.want, out) {
|
out := outptr.Elem().Interface()
|
||||||
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
|
if !reflect.DeepEqual(test.want, out) {
|
||||||
}
|
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiReturnWithStruct(t *testing.T) {
|
type methodMultiOutput struct {
|
||||||
|
Int *big.Int
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
|
||||||
const definition = `[
|
const definition = `[
|
||||||
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
|
||||||
|
var expected = methodMultiOutput{big.NewInt(1), "hello"}
|
||||||
|
|
||||||
abi, err := JSON(strings.NewReader(definition))
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
require.NoError(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// using buff to make the code readable
|
// using buff to make the code readable
|
||||||
buff := new(bytes.Buffer)
|
buff := new(bytes.Buffer)
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
|
||||||
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
|
||||||
stringOut := "hello"
|
buff.Write(common.RightPadBytes([]byte(expected.String), 32))
|
||||||
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
|
return abi, buff.Bytes(), expected
|
||||||
|
}
|
||||||
|
|
||||||
var inter struct {
|
func TestMethodMultiReturn(t *testing.T) {
|
||||||
Int *big.Int
|
type reversed struct {
|
||||||
String string
|
|
||||||
}
|
|
||||||
err = abi.Unpack(&inter, "multi", buff.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inter.Int == nil || inter.Int.Cmp(big.NewInt(1)) != 0 {
|
|
||||||
t.Error("expected Int to be 1 got", inter.Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
if inter.String != stringOut {
|
|
||||||
t.Error("expected String to be", stringOut, "got", inter.String)
|
|
||||||
}
|
|
||||||
|
|
||||||
var reversed struct {
|
|
||||||
String string
|
String string
|
||||||
Int *big.Int
|
Int *big.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
err = abi.Unpack(&reversed, "multi", buff.Bytes())
|
abi, data, expected := methodMultiReturn(require.New(t))
|
||||||
|
bigint := new(big.Int)
|
||||||
|
var testCases = []struct {
|
||||||
|
dest interface{}
|
||||||
|
expected interface{}
|
||||||
|
error string
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
&methodMultiOutput{},
|
||||||
|
&expected,
|
||||||
|
"",
|
||||||
|
"Can unpack into structure",
|
||||||
|
}, {
|
||||||
|
&reversed{},
|
||||||
|
&reversed{expected.String, expected.Int},
|
||||||
|
"",
|
||||||
|
"Can unpack into reversed structure",
|
||||||
|
}, {
|
||||||
|
&[]interface{}{&bigint, new(string)},
|
||||||
|
&[]interface{}{&expected.Int, &expected.String},
|
||||||
|
"",
|
||||||
|
"Can unpack into a slice",
|
||||||
|
}, {
|
||||||
|
&[2]interface{}{&bigint, new(string)},
|
||||||
|
&[2]interface{}{&expected.Int, &expected.String},
|
||||||
|
"",
|
||||||
|
"Can unpack into an array",
|
||||||
|
}, {
|
||||||
|
&[]interface{}{new(int), new(int)},
|
||||||
|
&[]interface{}{&expected.Int, &expected.String},
|
||||||
|
"abi: cannot unmarshal *big.Int in to int",
|
||||||
|
"Can not unpack into a slice with wrong types",
|
||||||
|
}, {
|
||||||
|
&[]interface{}{new(int)},
|
||||||
|
&[]interface{}{},
|
||||||
|
"abi: insufficient number of elements in the list/array for unpack, want 2, got 1",
|
||||||
|
"Can not unpack into a slice with wrong types",
|
||||||
|
}}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
err := abi.Unpack(tc.dest, "multi", data)
|
||||||
|
if tc.error == "" {
|
||||||
|
require.Nil(err, "Should be able to unpack method outputs.")
|
||||||
|
require.Equal(tc.expected, tc.dest)
|
||||||
|
} else {
|
||||||
|
require.EqualError(err, tc.error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiReturnWithArray(t *testing.T) {
|
||||||
|
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
buff := new(bytes.Buffer)
|
||||||
|
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009"))
|
||||||
|
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008"))
|
||||||
|
|
||||||
if reversed.Int == nil || reversed.Int.Cmp(big.NewInt(1)) != 0 {
|
ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9}
|
||||||
t.Error("expected Int to be 1 got", reversed.Int)
|
ret2, ret2Exp := new(uint64), uint64(8)
|
||||||
|
if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(*ret1, ret1Exp) {
|
||||||
if reversed.String != stringOut {
|
t.Error("array result", *ret1, "!= Expected", ret1Exp)
|
||||||
t.Error("expected String to be", stringOut, "got", reversed.String)
|
}
|
||||||
|
if *ret2 != ret2Exp {
|
||||||
|
t.Error("int result", *ret2, "!= Expected", ret2Exp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,11 +422,11 @@ func TestUnmarshal(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
} else {
|
} else {
|
||||||
if bytes.Compare(p0, p0Exp) != 0 {
|
if !bytes.Equal(p0, p0Exp) {
|
||||||
t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0)
|
t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Compare(p1[:], p1Exp) != 0 {
|
if !bytes.Equal(p1[:], p1Exp) {
|
||||||
t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1)
|
t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid hex in encSeed")
|
return nil, errors.New("invalid hex in encSeed")
|
||||||
}
|
}
|
||||||
|
if len(encSeedBytes) < 16 {
|
||||||
|
return nil, errors.New("invalid encSeed, too short")
|
||||||
|
}
|
||||||
iv := encSeedBytes[:16]
|
iv := encSeedBytes[:16]
|
||||||
cipherText := encSeedBytes[16:]
|
cipherText := encSeedBytes[16:]
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.2.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.9.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.9.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -260,8 +260,7 @@ func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree {
|
||||||
for d := 1; d <= depth(segmentCount); d++ {
|
for d := 1; d <= depth(segmentCount); d++ {
|
||||||
nodes := make([]*Node, count)
|
nodes := make([]*Node, count)
|
||||||
for i := 0; i < len(nodes); i++ {
|
for i := 0; i < len(nodes); i++ {
|
||||||
var parent *Node
|
parent := prevlevel[i/2]
|
||||||
parent = prevlevel[i/2]
|
|
||||||
t := NewNode(level, i, parent)
|
t := NewNode(level, i, parent)
|
||||||
nodes[i] = t
|
nodes[i] = t
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
build/ci.go
20
build/ci.go
|
|
@ -319,17 +319,25 @@ func doLint(cmdline []string) {
|
||||||
packages = flag.CommandLine.Args()
|
packages = flag.CommandLine.Args()
|
||||||
}
|
}
|
||||||
// Get metalinter and install all supported linters
|
// Get metalinter and install all supported linters
|
||||||
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v1"))
|
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v2"))
|
||||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), "--install")
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), "--install")
|
||||||
|
|
||||||
// Run fast linters batched together
|
// Run fast linters batched together
|
||||||
configs := []string{"--vendor", "--disable-all", "--enable=vet", "--enable=gofmt", "--enable=misspell"}
|
configs := []string{
|
||||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
|
"--vendor",
|
||||||
|
"--disable-all",
|
||||||
|
"--enable=vet",
|
||||||
|
"--enable=gofmt",
|
||||||
|
"--enable=misspell",
|
||||||
|
"--enable=goconst",
|
||||||
|
"--min-occurrences=6", // for goconst
|
||||||
|
}
|
||||||
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
||||||
|
|
||||||
// Run slow linters one by one
|
// Run slow linters one by one
|
||||||
for _, linter := range []string{"unconvert"} {
|
for _, linter := range []string{"unconvert", "gosimple"} {
|
||||||
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
|
||||||
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
|
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
41
cmd/ethkey/README.md
Normal file
41
cmd/ethkey/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
ethkey
|
||||||
|
======
|
||||||
|
|
||||||
|
ethkey is a simple command-line tool for working with Ethereum keyfiles.
|
||||||
|
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
### `ethkey generate`
|
||||||
|
|
||||||
|
Generate a new keyfile.
|
||||||
|
If you want to use an existing private key to use in the keyfile, it can be
|
||||||
|
specified by setting `--privatekey` with the location of the file containing the
|
||||||
|
private key.
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey inspect <keyfile>`
|
||||||
|
|
||||||
|
Print various information about the keyfile.
|
||||||
|
Private key information can be printed by using the `--private` flag;
|
||||||
|
make sure to use this feature with great caution!
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey sign <keyfile> <message/file>`
|
||||||
|
|
||||||
|
Sign the message with a keyfile.
|
||||||
|
It is possible to refer to a file containing the message.
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey verify <address> <signature> <message/file>`
|
||||||
|
|
||||||
|
Verify the signature of the message.
|
||||||
|
It is possible to refer to a file containing the message.
|
||||||
|
|
||||||
|
|
||||||
|
## Passphrases
|
||||||
|
|
||||||
|
For every command that uses a keyfile, you will be prompted to provide the
|
||||||
|
passphrase for decrypting the keyfile. To avoid this message, it is possible
|
||||||
|
to pass the passphrase by using the `--passphrase` flag pointing to a file that
|
||||||
|
contains the passphrase.
|
||||||
117
cmd/ethkey/generate.go
Normal file
117
cmd/ethkey/generate.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outputGenerate struct {
|
||||||
|
Address string
|
||||||
|
AddressEIP55 string
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandGenerate = cli.Command{
|
||||||
|
Name: "generate",
|
||||||
|
Usage: "generate new keyfile",
|
||||||
|
ArgsUsage: "[ <keyfile> ]",
|
||||||
|
Description: `
|
||||||
|
Generate a new keyfile.
|
||||||
|
If you want to use an existing private key to use in the keyfile, it can be
|
||||||
|
specified by setting --privatekey with the location of the file containing the
|
||||||
|
private key.`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
passphraseFlag,
|
||||||
|
jsonFlag,
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "privatekey",
|
||||||
|
Usage: "the file from where to read the private key to " +
|
||||||
|
"generate a keyfile for",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
// Check if keyfile path given and make sure it doesn't already exist.
|
||||||
|
keyfilepath := ctx.Args().First()
|
||||||
|
if keyfilepath == "" {
|
||||||
|
keyfilepath = defaultKeyfileName
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(keyfilepath); err == nil {
|
||||||
|
utils.Fatalf("Keyfile already exists at %s.", keyfilepath)
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
utils.Fatalf("Error checking if keyfile exists: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateKey *ecdsa.PrivateKey
|
||||||
|
|
||||||
|
// First check if a private key file is provided.
|
||||||
|
privateKeyFile := ctx.String("privatekey")
|
||||||
|
if privateKeyFile != "" {
|
||||||
|
privateKeyBytes, err := ioutil.ReadFile(privateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read the private key file '%s': %v",
|
||||||
|
privateKeyFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pk, err := crypto.HexToECDSA(string(privateKeyBytes))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(
|
||||||
|
"Could not construct ECDSA private key from file content: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
privateKey = pk
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not loaded, generate random.
|
||||||
|
if privateKey == nil {
|
||||||
|
pk, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to generate random private key: %v", err)
|
||||||
|
}
|
||||||
|
privateKey = pk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the keyfile object with a random UUID.
|
||||||
|
id := uuid.NewRandom()
|
||||||
|
key := &keystore.Key{
|
||||||
|
Id: id,
|
||||||
|
Address: crypto.PubkeyToAddress(privateKey.PublicKey),
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt key with passphrase.
|
||||||
|
passphrase := getPassPhrase(ctx, true)
|
||||||
|
keyjson, err := keystore.EncryptKey(key, passphrase,
|
||||||
|
keystore.StandardScryptN, keystore.StandardScryptP)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error encrypting key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the file to disk.
|
||||||
|
if err := os.MkdirAll(filepath.Dir(keyfilepath), 0700); err != nil {
|
||||||
|
utils.Fatalf("Could not create directory %s", filepath.Dir(keyfilepath))
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(keyfilepath, keyjson, 0600); err != nil {
|
||||||
|
utils.Fatalf("Failed to write keyfile to %s: %v", keyfilepath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output some information.
|
||||||
|
out := outputGenerate{
|
||||||
|
Address: key.Address.Hex(),
|
||||||
|
}
|
||||||
|
if ctx.Bool(jsonFlag.Name) {
|
||||||
|
mustPrintJSON(out)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Address: ", out.Address)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
74
cmd/ethkey/inspect.go
Normal file
74
cmd/ethkey/inspect.go
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outputInspect struct {
|
||||||
|
Address string
|
||||||
|
PublicKey string
|
||||||
|
PrivateKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandInspect = cli.Command{
|
||||||
|
Name: "inspect",
|
||||||
|
Usage: "inspect a keyfile",
|
||||||
|
ArgsUsage: "<keyfile>",
|
||||||
|
Description: `
|
||||||
|
Print various information about the keyfile.
|
||||||
|
Private key information can be printed by using the --private flag;
|
||||||
|
make sure to use this feature with great caution!`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
passphraseFlag,
|
||||||
|
jsonFlag,
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "private",
|
||||||
|
Usage: "include the private key in the output",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
keyfilepath := ctx.Args().First()
|
||||||
|
|
||||||
|
// Read key from file.
|
||||||
|
keyjson, err := ioutil.ReadFile(keyfilepath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read the keyfile at '%s': %v", keyfilepath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt key with passphrase.
|
||||||
|
passphrase := getPassPhrase(ctx, false)
|
||||||
|
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error decrypting key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output all relevant information we can retrieve.
|
||||||
|
showPrivate := ctx.Bool("private")
|
||||||
|
out := outputInspect{
|
||||||
|
Address: key.Address.Hex(),
|
||||||
|
PublicKey: hex.EncodeToString(
|
||||||
|
crypto.FromECDSAPub(&key.PrivateKey.PublicKey)),
|
||||||
|
}
|
||||||
|
if showPrivate {
|
||||||
|
out.PrivateKey = hex.EncodeToString(crypto.FromECDSA(key.PrivateKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Bool(jsonFlag.Name) {
|
||||||
|
mustPrintJSON(out)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Address: ", out.Address)
|
||||||
|
fmt.Println("Public key: ", out.PublicKey)
|
||||||
|
if showPrivate {
|
||||||
|
fmt.Println("Private key: ", out.PrivateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
70
cmd/ethkey/main.go
Normal file
70
cmd/ethkey/main.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultKeyfileName = "keyfile.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags)
|
||||||
|
|
||||||
|
app *cli.App // the main app instance
|
||||||
|
)
|
||||||
|
|
||||||
|
var ( // Commonly used command line flags.
|
||||||
|
passphraseFlag = cli.StringFlag{
|
||||||
|
Name: "passwordfile",
|
||||||
|
Usage: "the file that contains the passphrase for the keyfile",
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonFlag = cli.BoolFlag{
|
||||||
|
Name: "json",
|
||||||
|
Usage: "output JSON instead of human-readable format",
|
||||||
|
}
|
||||||
|
|
||||||
|
messageFlag = cli.StringFlag{
|
||||||
|
Name: "message",
|
||||||
|
Usage: "the file that contains the message to sign/verify",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configure the app instance.
|
||||||
|
func init() {
|
||||||
|
app = utils.NewApp(gitCommit, "an Ethereum key manager")
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
commandGenerate,
|
||||||
|
commandInspect,
|
||||||
|
commandSignMessage,
|
||||||
|
commandVerifyMessage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
148
cmd/ethkey/message.go
Normal file
148
cmd/ethkey/message.go
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outputSign struct {
|
||||||
|
Signature string
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandSignMessage = cli.Command{
|
||||||
|
Name: "signmessage",
|
||||||
|
Usage: "sign a message",
|
||||||
|
ArgsUsage: "<keyfile> <message/file>",
|
||||||
|
Description: `
|
||||||
|
Sign the message with a keyfile.
|
||||||
|
It is possible to refer to a file containing the message.`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
passphraseFlag,
|
||||||
|
jsonFlag,
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
keyfilepath := ctx.Args().First()
|
||||||
|
message := []byte(ctx.Args().Get(1))
|
||||||
|
|
||||||
|
// Load the keyfile.
|
||||||
|
keyjson, err := ioutil.ReadFile(keyfilepath)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read the keyfile at '%s': %v",
|
||||||
|
keyfilepath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrypt key with passphrase.
|
||||||
|
passphrase := getPassPhrase(ctx, false)
|
||||||
|
key, err := keystore.DecryptKey(keyjson, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error decrypting key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(message) == 0 {
|
||||||
|
utils.Fatalf("A message must be provided")
|
||||||
|
}
|
||||||
|
// Read message if file.
|
||||||
|
if _, err := os.Stat(string(message)); err == nil {
|
||||||
|
message, err = ioutil.ReadFile(string(message))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read the message file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signature, err := crypto.Sign(signHash(message), key.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to sign message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := outputSign{
|
||||||
|
Signature: hex.EncodeToString(signature),
|
||||||
|
}
|
||||||
|
if ctx.Bool(jsonFlag.Name) {
|
||||||
|
mustPrintJSON(out)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Signature: ", out.Signature)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputVerify struct {
|
||||||
|
Success bool
|
||||||
|
RecoveredAddress string
|
||||||
|
RecoveredPublicKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandVerifyMessage = cli.Command{
|
||||||
|
Name: "verifymessage",
|
||||||
|
Usage: "verify the signature of a signed message",
|
||||||
|
ArgsUsage: "<address> <signature> <message/file>",
|
||||||
|
Description: `
|
||||||
|
Verify the signature of the message.
|
||||||
|
It is possible to refer to a file containing the message.`,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
jsonFlag,
|
||||||
|
},
|
||||||
|
Action: func(ctx *cli.Context) error {
|
||||||
|
addressStr := ctx.Args().First()
|
||||||
|
signatureHex := ctx.Args().Get(1)
|
||||||
|
message := []byte(ctx.Args().Get(2))
|
||||||
|
|
||||||
|
// Determine whether it is a keyfile, public key or address.
|
||||||
|
if !common.IsHexAddress(addressStr) {
|
||||||
|
utils.Fatalf("Invalid address: %s", addressStr)
|
||||||
|
}
|
||||||
|
address := common.HexToAddress(addressStr)
|
||||||
|
|
||||||
|
signature, err := hex.DecodeString(signatureHex)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Signature encoding is not hexadecimal: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(message) == 0 {
|
||||||
|
utils.Fatalf("A message must be provided")
|
||||||
|
}
|
||||||
|
// Read message if file.
|
||||||
|
if _, err := os.Stat(string(message)); err == nil {
|
||||||
|
message, err = ioutil.ReadFile(string(message))
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read the message file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recoveredPubkey, err := crypto.SigToPub(signHash(message), signature)
|
||||||
|
if err != nil || recoveredPubkey == nil {
|
||||||
|
utils.Fatalf("Signature verification failed: %v", err)
|
||||||
|
}
|
||||||
|
recoveredPubkeyBytes := crypto.FromECDSAPub(recoveredPubkey)
|
||||||
|
recoveredAddress := crypto.PubkeyToAddress(*recoveredPubkey)
|
||||||
|
|
||||||
|
success := address == recoveredAddress
|
||||||
|
|
||||||
|
out := outputVerify{
|
||||||
|
Success: success,
|
||||||
|
RecoveredPublicKey: hex.EncodeToString(recoveredPubkeyBytes),
|
||||||
|
RecoveredAddress: strings.ToLower(recoveredAddress.Hex()),
|
||||||
|
}
|
||||||
|
if ctx.Bool(jsonFlag.Name) {
|
||||||
|
mustPrintJSON(out)
|
||||||
|
} else {
|
||||||
|
if out.Success {
|
||||||
|
fmt.Println("Signature verification successful!")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Signature verification failed!")
|
||||||
|
}
|
||||||
|
fmt.Println("Recovered public key: ", out.RecoveredPublicKey)
|
||||||
|
fmt.Println("Recovered address: ", out.RecoveredAddress)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
83
cmd/ethkey/utils.go
Normal file
83
cmd/ethkey/utils.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/console"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getPassPhrase obtains a passphrase given by the user. It first checks the
|
||||||
|
// --passphrase command line flag and ultimately prompts the user for a
|
||||||
|
// passphrase.
|
||||||
|
func getPassPhrase(ctx *cli.Context, confirmation bool) string {
|
||||||
|
// Look for the --passphrase flag.
|
||||||
|
passphraseFile := ctx.String(passphraseFlag.Name)
|
||||||
|
if passphraseFile != "" {
|
||||||
|
content, err := ioutil.ReadFile(passphraseFile)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase file '%s': %v",
|
||||||
|
passphraseFile, err)
|
||||||
|
}
|
||||||
|
return strings.TrimRight(string(content), "\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise prompt the user for the passphrase.
|
||||||
|
passphrase, err := console.Stdin.PromptPassword("Passphrase: ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||||
|
}
|
||||||
|
if confirmation {
|
||||||
|
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
||||||
|
}
|
||||||
|
if passphrase != confirm {
|
||||||
|
utils.Fatalf("Passphrases do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
// signHash is a helper function that calculates a hash for the given message
|
||||||
|
// that can be safely used to calculate a signature from.
|
||||||
|
//
|
||||||
|
// The hash is calulcated as
|
||||||
|
// keccak256("\x19Ethereum Signed Message:\n"${message length}${message}).
|
||||||
|
//
|
||||||
|
// This gives context to the signed message and prevents signing of transactions.
|
||||||
|
func signHash(data []byte) []byte {
|
||||||
|
msg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data)
|
||||||
|
return crypto.Keccak256([]byte(msg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// mustPrintJSON prints the JSON encoding of the given object and
|
||||||
|
// exits the program with an error message when the marshaling fails.
|
||||||
|
func mustPrintJSON(jsonObject interface{}) {
|
||||||
|
str, err := json.MarshalIndent(jsonObject, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to marshal JSON object: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(str))
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -35,6 +36,10 @@ func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
|
||||||
return &JSONLogger{json.NewEncoder(writer), cfg}
|
return &JSONLogger{json.NewEncoder(writer), cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CaptureState outputs state information on the logger.
|
// CaptureState outputs state information on the logger.
|
||||||
func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
|
func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
|
||||||
log := vm.StructLog{
|
log := vm.StructLog{
|
||||||
|
|
@ -56,6 +61,11 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
|
||||||
return l.encoder.Encode(log)
|
return l.encoder.Encode(log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CaptureFault outputs state information on the logger.
|
||||||
|
func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CaptureEnd is triggered at end of execution.
|
// CaptureEnd is triggered at end of execution.
|
||||||
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||||
type endLog struct {
|
type endLog struct {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/zlib"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
@ -506,7 +505,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
|
|
||||||
// Send an error if too frequent funding, othewise a success
|
// Send an error if too frequent funding, othewise a success
|
||||||
if !fund {
|
if !fund {
|
||||||
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil {
|
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil { // nolint: gosimple
|
||||||
log.Warn("Failed to send funding error to client", "err", err)
|
log.Warn("Failed to send funding error to client", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -698,11 +697,7 @@ func authTwitter(url string) (string, string, common.Address, error) {
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
reader, err := zlib.NewReader(res.Body)
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
if err != nil {
|
|
||||||
return "", "", common.Address{}, err
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(reader)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", common.Address{}, err
|
return "", "", common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
|
@ -114,8 +115,19 @@ func localConsole(ctx *cli.Context) error {
|
||||||
func remoteConsole(ctx *cli.Context) error {
|
func remoteConsole(ctx *cli.Context) error {
|
||||||
// Attach to a remotely running geth instance and start the JavaScript console
|
// Attach to a remotely running geth instance and start the JavaScript console
|
||||||
endpoint := ctx.Args().First()
|
endpoint := ctx.Args().First()
|
||||||
if endpoint == "" && ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
if endpoint == "" {
|
||||||
endpoint = fmt.Sprintf("%s/geth.ipc", ctx.GlobalString(utils.DataDirFlag.Name))
|
path := node.DefaultDataDir()
|
||||||
|
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
||||||
|
path = ctx.GlobalString(utils.DataDirFlag.Name)
|
||||||
|
}
|
||||||
|
if path != "" {
|
||||||
|
if ctx.GlobalBool(utils.TestnetFlag.Name) {
|
||||||
|
path = filepath.Join(path, "testnet")
|
||||||
|
} else if ctx.GlobalBool(utils.RinkebyFlag.Name) {
|
||||||
|
path = filepath.Join(path, "rinkeby")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endpoint = fmt.Sprintf("%s/geth.ipc", path)
|
||||||
}
|
}
|
||||||
client, err := dialRPC(endpoint)
|
client, err := dialRPC(endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -39,6 +39,8 @@ ADD genesis.json /genesis.json
|
||||||
ADD account.json /account.json
|
ADD account.json /account.json
|
||||||
ADD account.pass /account.pass
|
ADD account.pass /account.pass
|
||||||
|
|
||||||
|
EXPOSE 8080 30303 30303/udp
|
||||||
|
|
||||||
ENTRYPOINT [ \
|
ENTRYPOINT [ \
|
||||||
"faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
|
"faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
|
||||||
"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
|
"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
|
||||||
|
|
|
||||||
321
cmd/swarm/config.go
Normal file
321
cmd/swarm/config.go
Normal file
|
|
@ -0,0 +1,321 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
"github.com/naoina/toml"
|
||||||
|
|
||||||
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//flag definition for the dumpconfig command
|
||||||
|
DumpConfigCommand = cli.Command{
|
||||||
|
Action: utils.MigrateFlags(dumpConfig),
|
||||||
|
Name: "dumpconfig",
|
||||||
|
Usage: "Show configuration values",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Flags: app.Flags,
|
||||||
|
Category: "MISCELLANEOUS COMMANDS",
|
||||||
|
Description: `The dumpconfig command shows configuration values.`,
|
||||||
|
}
|
||||||
|
|
||||||
|
//flag definition for the config file command
|
||||||
|
SwarmTomlConfigPathFlag = cli.StringFlag{
|
||||||
|
Name: "config",
|
||||||
|
Usage: "TOML configuration file",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//constants for environment variables
|
||||||
|
const (
|
||||||
|
SWARM_ENV_CHEQUEBOOK_ADDR = "SWARM_CHEQUEBOOK_ADDR"
|
||||||
|
SWARM_ENV_ACCOUNT = "SWARM_ACCOUNT"
|
||||||
|
SWARM_ENV_LISTEN_ADDR = "SWARM_LISTEN_ADDR"
|
||||||
|
SWARM_ENV_PORT = "SWARM_PORT"
|
||||||
|
SWARM_ENV_NETWORK_ID = "SWARM_NETWORK_ID"
|
||||||
|
SWARM_ENV_SWAP_ENABLE = "SWARM_SWAP_ENABLE"
|
||||||
|
SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
|
||||||
|
SWARM_ENV_SYNC_ENABLE = "SWARM_SYNC_ENABLE"
|
||||||
|
SWARM_ENV_ENS_API = "SWARM_ENS_API"
|
||||||
|
SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR"
|
||||||
|
SWARM_ENV_CORS = "SWARM_CORS"
|
||||||
|
SWARM_ENV_BOOTNODES = "SWARM_BOOTNODES"
|
||||||
|
GETH_ENV_DATADIR = "GETH_DATADIR"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These settings ensure that TOML keys use the same names as Go struct fields.
|
||||||
|
var tomlSettings = toml.Config{
|
||||||
|
NormFieldName: func(rt reflect.Type, key string) string {
|
||||||
|
return key
|
||||||
|
},
|
||||||
|
FieldToKey: func(rt reflect.Type, field string) string {
|
||||||
|
return field
|
||||||
|
},
|
||||||
|
MissingField: func(rt reflect.Type, field string) error {
|
||||||
|
link := ""
|
||||||
|
if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" {
|
||||||
|
link = fmt.Sprintf(", check github.com/ethereum/go-ethereum/swarm/api/config.go for available fields")
|
||||||
|
}
|
||||||
|
return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//before booting the swarm node, build the configuration
|
||||||
|
func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
|
||||||
|
//check for deprecated flags
|
||||||
|
checkDeprecated(ctx)
|
||||||
|
//start by creating a default config
|
||||||
|
config = bzzapi.NewDefaultConfig()
|
||||||
|
//first load settings from config file (if provided)
|
||||||
|
config, err = configFileOverride(config, ctx)
|
||||||
|
//override settings provided by environment variables
|
||||||
|
config = envVarsOverride(config)
|
||||||
|
//override settings provided by command line
|
||||||
|
config = cmdLineOverride(config, ctx)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//finally, after the configuration build phase is finished, initialize
|
||||||
|
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context) {
|
||||||
|
//at this point, all vars should be set in the Config
|
||||||
|
//get the account for the provided swarm account
|
||||||
|
prvkey := getAccount(config.BzzAccount, ctx, stack)
|
||||||
|
//set the resolved config path (geth --datadir)
|
||||||
|
config.Path = stack.InstanceDir()
|
||||||
|
//finally, initialize the configuration
|
||||||
|
config.Init(prvkey)
|
||||||
|
//configuration phase completed here
|
||||||
|
log.Debug("Starting Swarm with the following parameters:")
|
||||||
|
//after having created the config, print it to screen
|
||||||
|
log.Debug(printConfig(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
//override the current config with whatever is in the config file, if a config file has been provided
|
||||||
|
func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
//only do something if the -config flag has been set
|
||||||
|
if ctx.GlobalIsSet(SwarmTomlConfigPathFlag.Name) {
|
||||||
|
var filepath string
|
||||||
|
if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" {
|
||||||
|
utils.Fatalf("Config file flag provided with invalid file path")
|
||||||
|
}
|
||||||
|
f, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
//decode the TOML file into a Config struct
|
||||||
|
//note that we are decoding into the existing defaultConfig;
|
||||||
|
//if an entry is not present in the file, the default entry is kept
|
||||||
|
err = tomlSettings.NewDecoder(f).Decode(&config)
|
||||||
|
// Add file name to errors that have a line number.
|
||||||
|
if _, ok := err.(*toml.LineError); ok {
|
||||||
|
err = errors.New(filepath + ", " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//override the current config with whatever is provided through the command line
|
||||||
|
//most values are not allowed a zero value (empty string), if not otherwise noted
|
||||||
|
func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
|
||||||
|
|
||||||
|
if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
|
||||||
|
currentConfig.BzzAccount = keyid
|
||||||
|
}
|
||||||
|
|
||||||
|
if chbookaddr := ctx.GlobalString(ChequebookAddrFlag.Name); chbookaddr != "" {
|
||||||
|
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
|
||||||
|
if id, _ := strconv.Atoi(networkid); id != 0 {
|
||||||
|
currentConfig.NetworkId = uint64(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
||||||
|
if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" {
|
||||||
|
currentConfig.Path = datadir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
|
||||||
|
if len(bzzport) > 0 {
|
||||||
|
currentConfig.Port = bzzport
|
||||||
|
}
|
||||||
|
|
||||||
|
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
|
||||||
|
currentConfig.ListenAddr = bzzaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(SwarmSwapEnabledFlag.Name) {
|
||||||
|
currentConfig.SwapEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(SwarmSyncEnabledFlag.Name) {
|
||||||
|
currentConfig.SyncEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
currentConfig.SwapApi = ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
||||||
|
if currentConfig.SwapEnabled && currentConfig.SwapApi == "" {
|
||||||
|
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
|
||||||
|
}
|
||||||
|
|
||||||
|
//EnsApi can be set to "", so can't check for empty string, as it is allowed!
|
||||||
|
if ctx.GlobalIsSet(EnsAPIFlag.Name) {
|
||||||
|
currentConfig.EnsApi = ctx.GlobalString(EnsAPIFlag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ensaddr := ctx.GlobalString(EnsAddrFlag.Name); ensaddr != "" {
|
||||||
|
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" {
|
||||||
|
currentConfig.Cors = cors
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
||||||
|
currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentConfig
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//override the current config with whatver is provided in environment variables
|
||||||
|
//most values are not allowed a zero value (empty string), if not otherwise noted
|
||||||
|
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||||
|
|
||||||
|
if keyid := os.Getenv(SWARM_ENV_ACCOUNT); keyid != "" {
|
||||||
|
currentConfig.BzzAccount = keyid
|
||||||
|
}
|
||||||
|
|
||||||
|
if chbookaddr := os.Getenv(SWARM_ENV_CHEQUEBOOK_ADDR); chbookaddr != "" {
|
||||||
|
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" {
|
||||||
|
if id, _ := strconv.Atoi(networkid); id != 0 {
|
||||||
|
currentConfig.NetworkId = uint64(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if datadir := os.Getenv(GETH_ENV_DATADIR); datadir != "" {
|
||||||
|
currentConfig.Path = datadir
|
||||||
|
}
|
||||||
|
|
||||||
|
bzzport := os.Getenv(SWARM_ENV_PORT)
|
||||||
|
if len(bzzport) > 0 {
|
||||||
|
currentConfig.Port = bzzport
|
||||||
|
}
|
||||||
|
|
||||||
|
if bzzaddr := os.Getenv(SWARM_ENV_LISTEN_ADDR); bzzaddr != "" {
|
||||||
|
currentConfig.ListenAddr = bzzaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
if swapenable := os.Getenv(SWARM_ENV_SWAP_ENABLE); swapenable != "" {
|
||||||
|
if swap, err := strconv.ParseBool(swapenable); err != nil {
|
||||||
|
currentConfig.SwapEnabled = swap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if syncenable := os.Getenv(SWARM_ENV_SYNC_ENABLE); syncenable != "" {
|
||||||
|
if sync, err := strconv.ParseBool(syncenable); err != nil {
|
||||||
|
currentConfig.SyncEnabled = sync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
|
||||||
|
currentConfig.SwapApi = swapapi
|
||||||
|
}
|
||||||
|
|
||||||
|
if currentConfig.SwapEnabled && currentConfig.SwapApi == "" {
|
||||||
|
utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API)
|
||||||
|
}
|
||||||
|
|
||||||
|
//EnsApi can be set to "", so can't check for empty string, as it is allowed
|
||||||
|
if ensapi, exists := os.LookupEnv(SWARM_ENV_ENS_API); exists {
|
||||||
|
currentConfig.EnsApi = ensapi
|
||||||
|
}
|
||||||
|
|
||||||
|
if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" {
|
||||||
|
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cors := os.Getenv(SWARM_ENV_CORS); cors != "" {
|
||||||
|
currentConfig.Cors = cors
|
||||||
|
}
|
||||||
|
|
||||||
|
if bootnodes := os.Getenv(SWARM_ENV_BOOTNODES); bootnodes != "" {
|
||||||
|
currentConfig.BootNodes = bootnodes
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpConfig is the dumpconfig command.
|
||||||
|
// writes a default config to STDOUT
|
||||||
|
func dumpConfig(ctx *cli.Context) error {
|
||||||
|
cfg, err := buildConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf(fmt.Sprintf("Uh oh - dumpconfig triggered an error %v", err))
|
||||||
|
}
|
||||||
|
comment := ""
|
||||||
|
out, err := tomlSettings.Marshal(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
io.WriteString(os.Stdout, comment)
|
||||||
|
os.Stdout.Write(out)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//deprecated flags checked here
|
||||||
|
func checkDeprecated(ctx *cli.Context) {
|
||||||
|
// exit if the deprecated --ethapi flag is set
|
||||||
|
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
|
||||||
|
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//print a Config as string
|
||||||
|
func printConfig(config *bzzapi.Config) string {
|
||||||
|
out, err := tomlSettings.Marshal(&config)
|
||||||
|
if err != nil {
|
||||||
|
return (fmt.Sprintf("Something is not right with the configuration: %v", err))
|
||||||
|
}
|
||||||
|
return string(out)
|
||||||
|
}
|
||||||
459
cmd/swarm/config_test.go
Normal file
459
cmd/swarm/config_test.go
Normal file
|
|
@ -0,0 +1,459 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDumpConfig(t *testing.T) {
|
||||||
|
swarm := runSwarm(t, "dumpconfig")
|
||||||
|
defaultConf := api.NewDefaultConfig()
|
||||||
|
out, err := tomlSettings.Marshal(&defaultConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
swarm.Expect(string(out))
|
||||||
|
swarm.ExpectExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailsSwapEnabledNoSwapApi(t *testing.T) {
|
||||||
|
flags := []string{
|
||||||
|
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||||
|
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||||
|
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
swarm := runSwarm(t, flags...)
|
||||||
|
swarm.Expect("Fatal: " + SWARM_ERR_SWAP_SET_NO_API + "\n")
|
||||||
|
swarm.ExpectExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailsNoBzzAccount(t *testing.T) {
|
||||||
|
flags := []string{
|
||||||
|
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||||
|
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||||
|
}
|
||||||
|
|
||||||
|
swarm := runSwarm(t, flags...)
|
||||||
|
swarm.Expect("Fatal: " + SWARM_ERR_NO_BZZACCOUNT + "\n")
|
||||||
|
swarm.ExpectExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCmdLineOverrides(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "bzztest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
conf, account := getTestAccount(t, dir)
|
||||||
|
node := &testNode{Dir: dir}
|
||||||
|
|
||||||
|
// assign ports
|
||||||
|
httpPort, err := assignTCPPort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := []string{
|
||||||
|
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||||
|
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||||
|
fmt.Sprintf("--%s", SwarmSyncEnabledFlag.Name),
|
||||||
|
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
|
||||||
|
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||||
|
fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
|
||||||
|
"--datadir", dir,
|
||||||
|
"--ipcpath", conf.IPCPath,
|
||||||
|
}
|
||||||
|
node.Cmd = runSwarm(t, flags...)
|
||||||
|
node.Cmd.InputLine(testPassphrase)
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// wait for the node to start
|
||||||
|
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||||
|
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Client == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load info
|
||||||
|
var info swarm.Info
|
||||||
|
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Port != httpPort {
|
||||||
|
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.NetworkId != 42 {
|
||||||
|
t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.SyncEnabled {
|
||||||
|
t.Fatal("Expected Sync to be enabled, but is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Cors != "*" {
|
||||||
|
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileOverrides(t *testing.T) {
|
||||||
|
|
||||||
|
// assign ports
|
||||||
|
httpPort, err := assignTCPPort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//create a config file
|
||||||
|
//first, create a default conf
|
||||||
|
defaultConf := api.NewDefaultConfig()
|
||||||
|
//change some values in order to test if they have been loaded
|
||||||
|
defaultConf.SyncEnabled = true
|
||||||
|
defaultConf.NetworkId = 54
|
||||||
|
defaultConf.Port = httpPort
|
||||||
|
defaultConf.StoreParams.DbCapacity = 9000000
|
||||||
|
defaultConf.ChunkerParams.Branches = 64
|
||||||
|
defaultConf.HiveParams.CallInterval = 6000000000
|
||||||
|
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||||
|
defaultConf.SyncParams.KeyBufferSize = 512
|
||||||
|
//create a TOML string
|
||||||
|
out, err := tomlSettings.Marshal(&defaultConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||||
|
}
|
||||||
|
//create file
|
||||||
|
f, err := ioutil.TempFile("", "testconfig.toml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||||
|
}
|
||||||
|
//write file
|
||||||
|
_, err = f.WriteString(string(out))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||||
|
}
|
||||||
|
f.Sync()
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "bzztest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
conf, account := getTestAccount(t, dir)
|
||||||
|
node := &testNode{Dir: dir}
|
||||||
|
|
||||||
|
flags := []string{
|
||||||
|
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
|
||||||
|
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||||
|
"--ens-api", "",
|
||||||
|
"--ipcpath", conf.IPCPath,
|
||||||
|
"--datadir", dir,
|
||||||
|
}
|
||||||
|
node.Cmd = runSwarm(t, flags...)
|
||||||
|
node.Cmd.InputLine(testPassphrase)
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// wait for the node to start
|
||||||
|
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||||
|
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Client == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load info
|
||||||
|
var info swarm.Info
|
||||||
|
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Port != httpPort {
|
||||||
|
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.NetworkId != 54 {
|
||||||
|
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.SyncEnabled {
|
||||||
|
t.Fatal("Expected Sync to be enabled, but is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.StoreParams.DbCapacity != 9000000 {
|
||||||
|
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.ChunkerParams.Branches != 64 {
|
||||||
|
t.Fatalf("Expected chunker params branches to be %d, got %d", 64, info.ChunkerParams.Branches)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.HiveParams.CallInterval != 6000000000 {
|
||||||
|
t.Fatalf("Expected HiveParams CallInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.CallInterval))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
|
||||||
|
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.SyncParams.KeyBufferSize != 512 {
|
||||||
|
t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvVars(t *testing.T) {
|
||||||
|
// assign ports
|
||||||
|
httpPort, err := assignTCPPort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envVars := os.Environ()
|
||||||
|
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort))
|
||||||
|
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999"))
|
||||||
|
envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*"))
|
||||||
|
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncEnabledFlag.EnvVar, "true"))
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "bzztest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
conf, account := getTestAccount(t, dir)
|
||||||
|
node := &testNode{Dir: dir}
|
||||||
|
flags := []string{
|
||||||
|
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||||
|
"--ens-api", "",
|
||||||
|
"--datadir", dir,
|
||||||
|
"--ipcpath", conf.IPCPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
//node.Cmd = runSwarm(t,flags...)
|
||||||
|
//node.Cmd.cmd.Env = envVars
|
||||||
|
//the above assignment does not work, so we need a custom Cmd here in order to pass envVars:
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Path: reexec.Self(),
|
||||||
|
Args: append([]string{"swarm-test"}, flags...),
|
||||||
|
Stderr: os.Stderr,
|
||||||
|
Stdout: os.Stdout,
|
||||||
|
}
|
||||||
|
cmd.Env = envVars
|
||||||
|
//stdout, err := cmd.StdoutPipe()
|
||||||
|
//if err != nil {
|
||||||
|
// t.Fatal(err)
|
||||||
|
//}
|
||||||
|
//stdout = bufio.NewReader(stdout)
|
||||||
|
var stdin io.WriteCloser
|
||||||
|
if stdin, err = cmd.StdinPipe(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//cmd.InputLine(testPassphrase)
|
||||||
|
io.WriteString(stdin, testPassphrase+"\n")
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
node.Shutdown()
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// wait for the node to start
|
||||||
|
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||||
|
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Client == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load info
|
||||||
|
var info swarm.Info
|
||||||
|
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Port != httpPort {
|
||||||
|
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.NetworkId != 999 {
|
||||||
|
t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Cors != "*" {
|
||||||
|
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.SyncEnabled {
|
||||||
|
t.Fatal("Expected Sync to be enabled, but is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Shutdown()
|
||||||
|
cmd.Process.Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCmdLineOverridesFile(t *testing.T) {
|
||||||
|
|
||||||
|
// assign ports
|
||||||
|
httpPort, err := assignTCPPort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//create a config file
|
||||||
|
//first, create a default conf
|
||||||
|
defaultConf := api.NewDefaultConfig()
|
||||||
|
//change some values in order to test if they have been loaded
|
||||||
|
defaultConf.SyncEnabled = false
|
||||||
|
defaultConf.NetworkId = 54
|
||||||
|
defaultConf.Port = "8588"
|
||||||
|
defaultConf.StoreParams.DbCapacity = 9000000
|
||||||
|
defaultConf.ChunkerParams.Branches = 64
|
||||||
|
defaultConf.HiveParams.CallInterval = 6000000000
|
||||||
|
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||||
|
defaultConf.SyncParams.KeyBufferSize = 512
|
||||||
|
//create a TOML file
|
||||||
|
out, err := tomlSettings.Marshal(&defaultConf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||||
|
}
|
||||||
|
//write file
|
||||||
|
f, err := ioutil.TempFile("", "testconfig.toml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||||
|
}
|
||||||
|
//write file
|
||||||
|
_, err = f.WriteString(string(out))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||||
|
}
|
||||||
|
f.Sync()
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "bzztest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
conf, account := getTestAccount(t, dir)
|
||||||
|
node := &testNode{Dir: dir}
|
||||||
|
|
||||||
|
expectNetworkId := uint64(77)
|
||||||
|
|
||||||
|
flags := []string{
|
||||||
|
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77",
|
||||||
|
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||||
|
fmt.Sprintf("--%s", SwarmSyncEnabledFlag.Name),
|
||||||
|
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
|
||||||
|
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||||
|
"--ens-api", "",
|
||||||
|
"--datadir", dir,
|
||||||
|
"--ipcpath", conf.IPCPath,
|
||||||
|
}
|
||||||
|
node.Cmd = runSwarm(t, flags...)
|
||||||
|
node.Cmd.InputLine(testPassphrase)
|
||||||
|
defer func() {
|
||||||
|
if t.Failed() {
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
// wait for the node to start
|
||||||
|
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||||
|
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Client == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load info
|
||||||
|
var info swarm.Info
|
||||||
|
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Port != httpPort {
|
||||||
|
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.NetworkId != expectNetworkId {
|
||||||
|
t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.SyncEnabled {
|
||||||
|
t.Fatal("Expected Sync to be enabled, but is false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.StoreParams.DbCapacity != 9000000 {
|
||||||
|
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.ChunkerParams.Branches != 64 {
|
||||||
|
t.Fatalf("Expected chunker params branches to be %d, got %d", 64, info.ChunkerParams.Branches)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.HiveParams.CallInterval != 6000000000 {
|
||||||
|
t.Fatalf("Expected HiveParams CallInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.CallInterval))
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
|
||||||
|
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.SyncParams.KeyBufferSize != 512 {
|
||||||
|
t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Shutdown()
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -66,49 +67,58 @@ var (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ChequebookAddrFlag = cli.StringFlag{
|
ChequebookAddrFlag = cli.StringFlag{
|
||||||
Name: "chequebook",
|
Name: "chequebook",
|
||||||
Usage: "chequebook contract address",
|
Usage: "chequebook contract address",
|
||||||
|
EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
|
||||||
}
|
}
|
||||||
SwarmAccountFlag = cli.StringFlag{
|
SwarmAccountFlag = cli.StringFlag{
|
||||||
Name: "bzzaccount",
|
Name: "bzzaccount",
|
||||||
Usage: "Swarm account key file",
|
Usage: "Swarm account key file",
|
||||||
|
EnvVar: SWARM_ENV_ACCOUNT,
|
||||||
}
|
}
|
||||||
SwarmListenAddrFlag = cli.StringFlag{
|
SwarmListenAddrFlag = cli.StringFlag{
|
||||||
Name: "httpaddr",
|
Name: "httpaddr",
|
||||||
Usage: "Swarm HTTP API listening interface",
|
Usage: "Swarm HTTP API listening interface",
|
||||||
|
EnvVar: SWARM_ENV_LISTEN_ADDR,
|
||||||
}
|
}
|
||||||
SwarmPortFlag = cli.StringFlag{
|
SwarmPortFlag = cli.StringFlag{
|
||||||
Name: "bzzport",
|
Name: "bzzport",
|
||||||
Usage: "Swarm local http api port",
|
Usage: "Swarm local http api port",
|
||||||
|
EnvVar: SWARM_ENV_PORT,
|
||||||
}
|
}
|
||||||
SwarmNetworkIdFlag = cli.IntFlag{
|
SwarmNetworkIdFlag = cli.IntFlag{
|
||||||
Name: "bzznetworkid",
|
Name: "bzznetworkid",
|
||||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||||
|
EnvVar: SWARM_ENV_NETWORK_ID,
|
||||||
}
|
}
|
||||||
SwarmConfigPathFlag = cli.StringFlag{
|
SwarmConfigPathFlag = cli.StringFlag{
|
||||||
Name: "bzzconfig",
|
Name: "bzzconfig",
|
||||||
Usage: "Swarm config file path (datadir/bzz)",
|
Usage: "DEPRECATED: please use --config path/to/TOML-file",
|
||||||
}
|
}
|
||||||
SwarmSwapEnabledFlag = cli.BoolFlag{
|
SwarmSwapEnabledFlag = cli.BoolFlag{
|
||||||
Name: "swap",
|
Name: "swap",
|
||||||
Usage: "Swarm SWAP enabled (default false)",
|
Usage: "Swarm SWAP enabled (default false)",
|
||||||
|
EnvVar: SWARM_ENV_SWAP_ENABLE,
|
||||||
}
|
}
|
||||||
SwarmSwapAPIFlag = cli.StringFlag{
|
SwarmSwapAPIFlag = cli.StringFlag{
|
||||||
Name: "swap-api",
|
Name: "swap-api",
|
||||||
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||||
|
EnvVar: SWARM_ENV_SWAP_API,
|
||||||
}
|
}
|
||||||
SwarmSyncEnabledFlag = cli.BoolTFlag{
|
SwarmSyncEnabledFlag = cli.BoolTFlag{
|
||||||
Name: "sync",
|
Name: "sync",
|
||||||
Usage: "Swarm Syncing enabled (default true)",
|
Usage: "Swarm Syncing enabled (default true)",
|
||||||
|
EnvVar: SWARM_ENV_SYNC_ENABLE,
|
||||||
}
|
}
|
||||||
EnsAPIFlag = cli.StringFlag{
|
EnsAPIFlag = cli.StringFlag{
|
||||||
Name: "ens-api",
|
Name: "ens-api",
|
||||||
Usage: "URL of the Ethereum API provider to use for ENS record lookups",
|
Usage: "URL of the Ethereum API provider to use for ENS record lookups",
|
||||||
Value: node.DefaultIPCEndpoint("geth"),
|
EnvVar: SWARM_ENV_ENS_API,
|
||||||
}
|
}
|
||||||
EnsAddrFlag = cli.StringFlag{
|
EnsAddrFlag = cli.StringFlag{
|
||||||
Name: "ens-addr",
|
Name: "ens-addr",
|
||||||
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
|
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
|
||||||
|
EnvVar: SWARM_ENV_ENS_ADDR,
|
||||||
}
|
}
|
||||||
SwarmApiFlag = cli.StringFlag{
|
SwarmApiFlag = cli.StringFlag{
|
||||||
Name: "bzzapi",
|
Name: "bzzapi",
|
||||||
|
|
@ -136,8 +146,9 @@ var (
|
||||||
Usage: "force mime type",
|
Usage: "force mime type",
|
||||||
}
|
}
|
||||||
CorsStringFlag = cli.StringFlag{
|
CorsStringFlag = cli.StringFlag{
|
||||||
Name: "corsdomain",
|
Name: "corsdomain",
|
||||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||||
|
EnvVar: SWARM_ENV_CORS,
|
||||||
}
|
}
|
||||||
|
|
||||||
// the following flags are deprecated and should be removed in the future
|
// the following flags are deprecated and should be removed in the future
|
||||||
|
|
@ -147,6 +158,12 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//declare a few constant error messages, useful for later error check comparisons in test
|
||||||
|
var (
|
||||||
|
SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
||||||
|
SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
|
||||||
|
)
|
||||||
|
|
||||||
var defaultNodeConfig = node.DefaultConfig
|
var defaultNodeConfig = node.DefaultConfig
|
||||||
|
|
||||||
// This init function sets defaults so cmd/swarm can run alongside geth.
|
// This init function sets defaults so cmd/swarm can run alongside geth.
|
||||||
|
|
@ -302,6 +319,8 @@ Remove corrupt entries from a local chunk database.
|
||||||
DEPRECATED: use 'swarm db clean'.
|
DEPRECATED: use 'swarm db clean'.
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
// See config.go
|
||||||
|
DumpConfigCommand,
|
||||||
}
|
}
|
||||||
sort.Sort(cli.CommandsByName(app.Commands))
|
sort.Sort(cli.CommandsByName(app.Commands))
|
||||||
|
|
||||||
|
|
@ -325,6 +344,7 @@ DEPRECATED: use 'swarm db clean'.
|
||||||
CorsStringFlag,
|
CorsStringFlag,
|
||||||
EnsAPIFlag,
|
EnsAPIFlag,
|
||||||
EnsAddrFlag,
|
EnsAddrFlag,
|
||||||
|
SwarmTomlConfigPathFlag,
|
||||||
SwarmConfigPathFlag,
|
SwarmConfigPathFlag,
|
||||||
SwarmSwapEnabledFlag,
|
SwarmSwapEnabledFlag,
|
||||||
SwarmSwapAPIFlag,
|
SwarmSwapAPIFlag,
|
||||||
|
|
@ -377,19 +397,32 @@ func version(ctx *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func bzzd(ctx *cli.Context) error {
|
func bzzd(ctx *cli.Context) error {
|
||||||
// exit if the deprecated --ethapi flag is set
|
//build a valid bzzapi.Config from all available sources:
|
||||||
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
|
//default config, file config, command line and env vars
|
||||||
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
|
bzzconfig, err := buildConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("unable to configure swarm: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := defaultNodeConfig
|
cfg := defaultNodeConfig
|
||||||
|
//geth only supports --datadir via command line
|
||||||
|
//in order to be consistent within swarm, if we pass --datadir via environment variable
|
||||||
|
//or via config file, we get the same directory for geth and swarm
|
||||||
|
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
||||||
|
cfg.DataDir = bzzconfig.Path
|
||||||
|
}
|
||||||
|
//setup the ethereum node
|
||||||
utils.SetNodeConfig(ctx, &cfg)
|
utils.SetNodeConfig(ctx, &cfg)
|
||||||
stack, err := node.New(&cfg)
|
stack, err := node.New(&cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("can't create node: %v", err)
|
utils.Fatalf("can't create node: %v", err)
|
||||||
}
|
}
|
||||||
|
//a few steps need to be done after the config phase is completed,
|
||||||
registerBzzService(ctx, stack)
|
//due to overriding behavior
|
||||||
|
initSwarmNode(bzzconfig, stack, ctx)
|
||||||
|
//register BZZ as node.Service in the ethereum node
|
||||||
|
registerBzzService(bzzconfig, ctx, stack)
|
||||||
|
//start the node
|
||||||
utils.StartNode(stack)
|
utils.StartNode(stack)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|
@ -401,13 +434,12 @@ func bzzd(ctx *cli.Context) error {
|
||||||
stack.Stop()
|
stack.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
|
|
||||||
// Add bootnodes as initial peers.
|
// Add bootnodes as initial peers.
|
||||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
if bzzconfig.BootNodes != "" {
|
||||||
bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
|
bootnodes := strings.Split(bzzconfig.BootNodes, ",")
|
||||||
injectBootnodes(stack.Server(), bootnodes)
|
injectBootnodes(stack.Server(), bootnodes)
|
||||||
} else {
|
} else {
|
||||||
if networkId == 3 {
|
if bzzconfig.NetworkId == 3 {
|
||||||
injectBootnodes(stack.Server(), testbetBootNodes)
|
injectBootnodes(stack.Server(), testbetBootNodes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -448,61 +480,31 @@ func detectEnsAddr(client *rpc.Client) (common.Address, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
|
||||||
prvkey := getAccount(ctx, stack)
|
|
||||||
|
|
||||||
chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
|
|
||||||
bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name)
|
|
||||||
if bzzdir == "" {
|
|
||||||
bzzdir = stack.InstanceDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name))
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("unable to configure swarm: %v", err)
|
|
||||||
}
|
|
||||||
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
|
|
||||||
if len(bzzport) > 0 {
|
|
||||||
bzzconfig.Port = bzzport
|
|
||||||
}
|
|
||||||
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
|
|
||||||
bzzconfig.ListenAddr = bzzaddr
|
|
||||||
}
|
|
||||||
swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
|
|
||||||
syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name)
|
|
||||||
|
|
||||||
swapapi := ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
|
||||||
if swapEnabled && swapapi == "" {
|
|
||||||
utils.Fatalf("SWAP is enabled but --swap-api is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
ensapi := ctx.GlobalString(EnsAPIFlag.Name)
|
|
||||||
ensAddr := ctx.GlobalString(EnsAddrFlag.Name)
|
|
||||||
|
|
||||||
cors := ctx.GlobalString(CorsStringFlag.Name)
|
|
||||||
|
|
||||||
|
//define the swarm service boot function
|
||||||
boot := func(ctx *node.ServiceContext) (node.Service, error) {
|
boot := func(ctx *node.ServiceContext) (node.Service, error) {
|
||||||
var swapClient *ethclient.Client
|
var swapClient *ethclient.Client
|
||||||
if swapapi != "" {
|
var err error
|
||||||
log.Info("connecting to SWAP API", "url", swapapi)
|
if bzzconfig.SwapApi != "" {
|
||||||
swapClient, err = ethclient.Dial(swapapi)
|
log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi)
|
||||||
|
swapClient, err = ethclient.Dial(bzzconfig.SwapApi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", swapapi, err)
|
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ensClient *ethclient.Client
|
var ensClient *ethclient.Client
|
||||||
if ensapi != "" {
|
if bzzconfig.EnsApi != "" {
|
||||||
log.Info("connecting to ENS API", "url", ensapi)
|
log.Info("connecting to ENS API", "url", bzzconfig.EnsApi)
|
||||||
client, err := rpc.Dial(ensapi)
|
client, err := rpc.Dial(bzzconfig.EnsApi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error connecting to ENS API %s: %s", ensapi, err)
|
return nil, fmt.Errorf("error connecting to ENS API %s: %s", bzzconfig.EnsApi, err)
|
||||||
}
|
}
|
||||||
ensClient = ethclient.NewClient(client)
|
ensClient = ethclient.NewClient(client)
|
||||||
|
|
||||||
if ensAddr != "" {
|
//no ENS root address set yet
|
||||||
bzzconfig.EnsRoot = common.HexToAddress(ensAddr)
|
if bzzconfig.EnsRoot == (common.Address{}) {
|
||||||
} else {
|
|
||||||
ensAddr, err := detectEnsAddr(client)
|
ensAddr, err := detectEnsAddr(client)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bzzconfig.EnsRoot = ensAddr
|
bzzconfig.EnsRoot = ensAddr
|
||||||
|
|
@ -512,21 +514,21 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, swapEnabled, syncEnabled, cors)
|
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, bzzconfig.SwapEnabled, bzzconfig.SyncEnabled, bzzconfig.Cors)
|
||||||
}
|
}
|
||||||
|
//register within the ethereum node
|
||||||
if err := stack.Register(boot); err != nil {
|
if err := stack.Register(boot); err != nil {
|
||||||
utils.Fatalf("Failed to register the Swarm service: %v", err)
|
utils.Fatalf("Failed to register the Swarm service: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||||
keyid := ctx.GlobalString(SwarmAccountFlag.Name)
|
//an account is mandatory
|
||||||
|
if bzzaccount == "" {
|
||||||
if keyid == "" {
|
utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
|
||||||
utils.Fatalf("Option %q is required", SwarmAccountFlag.Name)
|
|
||||||
}
|
}
|
||||||
// Try to load the arg as a hex key file.
|
// Try to load the arg as a hex key file.
|
||||||
if key, err := crypto.LoadECDSA(keyid); err == nil {
|
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
|
||||||
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
@ -534,7 +536,7 @@ func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||||
am := stack.AccountManager()
|
am := stack.AccountManager()
|
||||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
||||||
return decryptStoreAccount(ks, keyid, utils.MakePasswordList(ctx))
|
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
|
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
|
||||||
|
|
@ -552,7 +554,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
|
||||||
utils.Fatalf("Can't find swarm account key %s", account)
|
utils.Fatalf("Can't find swarm account key %s", account)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Can't find swarm account key: %v", err)
|
utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
|
||||||
}
|
}
|
||||||
keyjson, err := ioutil.ReadFile(a.URL.Path)
|
keyjson, err := ioutil.ReadFile(a.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const bzzManifestJSON = "application/bzz-manifest+json"
|
||||||
|
|
||||||
func add(ctx *cli.Context) {
|
func add(ctx *cli.Context) {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
if len(args) < 3 {
|
if len(args) < 3 {
|
||||||
|
|
@ -145,7 +147,7 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
|
||||||
if path == entry.Path {
|
if path == entry.Path {
|
||||||
utils.Fatalf("Path %s already present, not adding anything", path)
|
utils.Fatalf("Path %s already present, not adding anything", path)
|
||||||
} else {
|
} else {
|
||||||
if entry.ContentType == "application/bzz-manifest+json" {
|
if entry.ContentType == bzzManifestJSON {
|
||||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||||
longestPathEntry = entry
|
longestPathEntry = entry
|
||||||
|
|
@ -207,7 +209,7 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
|
||||||
if path == entry.Path {
|
if path == entry.Path {
|
||||||
newEntry = entry
|
newEntry = entry
|
||||||
} else {
|
} else {
|
||||||
if entry.ContentType == "application/bzz-manifest+json" {
|
if entry.ContentType == bzzManifestJSON {
|
||||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||||
longestPathEntry = entry
|
longestPathEntry = entry
|
||||||
|
|
@ -281,7 +283,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
|
||||||
if path == entry.Path {
|
if path == entry.Path {
|
||||||
entryToRemove = entry
|
entryToRemove = entry
|
||||||
} else {
|
} else {
|
||||||
if entry.ContentType == "application/bzz-manifest+json" {
|
if entry.ContentType == bzzManifestJSON {
|
||||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||||
longestPathEntry = entry
|
longestPathEntry = entry
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/reexec"
|
"github.com/docker/docker/pkg/reexec"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
|
@ -156,9 +157,9 @@ type testNode struct {
|
||||||
|
|
||||||
const testPassphrase = "swarm-test-passphrase"
|
const testPassphrase = "swarm-test-passphrase"
|
||||||
|
|
||||||
func newTestNode(t *testing.T, dir string) *testNode {
|
func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
|
||||||
// create key
|
// create key
|
||||||
conf := &node.Config{
|
conf = &node.Config{
|
||||||
DataDir: dir,
|
DataDir: dir,
|
||||||
IPCPath: "bzzd.ipc",
|
IPCPath: "bzzd.ipc",
|
||||||
NoUSB: true,
|
NoUSB: true,
|
||||||
|
|
@ -167,18 +168,24 @@ func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
account, err := n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
|
account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
node := &testNode{Dir: dir}
|
|
||||||
|
|
||||||
// use a unique IPCPath when running tests on Windows
|
// use a unique IPCPath when running tests on Windows
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
|
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return conf, account
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
|
|
||||||
|
conf, account := getTestAccount(t, dir)
|
||||||
|
node := &testNode{Dir: dir}
|
||||||
|
|
||||||
// assign ports
|
// assign ports
|
||||||
httpPort, err := assignTCPPort()
|
httpPort, err := assignTCPPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,13 @@ func getFdLimit() (int, error) {
|
||||||
}
|
}
|
||||||
return int(limit.Cur), nil
|
return int(limit.Cur), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func getFdMaxLimit() (int, error) {
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(limit.Max), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,22 @@
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
|
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
|
||||||
// per this process can be retrieved.
|
// per this process can be retrieved.
|
||||||
func TestFileDescriptorLimits(t *testing.T) {
|
func TestFileDescriptorLimits(t *testing.T) {
|
||||||
target := 4096
|
target := 4096
|
||||||
|
hardlimit, err := getFdMaxLimit()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hardlimit < target {
|
||||||
|
t.Skip(fmt.Sprintf("system limit is less than desired test target: %d < %d", hardlimit, target))
|
||||||
|
}
|
||||||
|
|
||||||
if limit, err := getFdLimit(); err != nil || limit <= 0 {
|
if limit, err := getFdLimit(); err != nil || limit <= 0 {
|
||||||
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
|
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,13 @@ func getFdLimit() (int, error) {
|
||||||
}
|
}
|
||||||
return int(limit.Cur), nil
|
return int(limit.Cur), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func getFdMaxLimit() (int, error) {
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(limit.Max), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,9 @@ func getFdLimit() (int, error) {
|
||||||
// Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
|
// Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
|
||||||
return 16384, nil
|
return 16384, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func getFdMaxLimit() (int, error) {
|
||||||
|
return getFdLimit()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -746,6 +746,12 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
|
||||||
if err != nil || index < 0 {
|
if err != nil || index < 0 {
|
||||||
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
|
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
|
||||||
}
|
}
|
||||||
|
log.Warn("-------------------------------------------------------------------")
|
||||||
|
log.Warn("Referring to accounts by order in the keystore folder is dangerous!")
|
||||||
|
log.Warn("This functionality is deprecated and will be removed in the future!")
|
||||||
|
log.Warn("Please use explicit addresses! (can search via `geth account list`)")
|
||||||
|
log.Warn("-------------------------------------------------------------------")
|
||||||
|
|
||||||
accs := ks.Accounts()
|
accs := ks.Accounts()
|
||||||
if len(accs) <= index {
|
if len(accs) <= index {
|
||||||
return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs))
|
return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs))
|
||||||
|
|
@ -762,15 +768,6 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
||||||
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
|
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
|
||||||
}
|
}
|
||||||
cfg.Etherbase = account.Address
|
cfg.Etherbase = account.Address
|
||||||
return
|
|
||||||
}
|
|
||||||
accounts := ks.Accounts()
|
|
||||||
if (cfg.Etherbase == common.Address{}) {
|
|
||||||
if len(accounts) > 0 {
|
|
||||||
cfg.Etherbase = accounts[0].Address
|
|
||||||
} else {
|
|
||||||
log.Warn("No etherbase set and no accounts found as default")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,7 @@
|
||||||
// Package common contains various helper functions.
|
// Package common contains various helper functions.
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import "encoding/hex"
|
||||||
"encoding/hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ToHex(b []byte) string {
|
func ToHex(b []byte) string {
|
||||||
hex := Bytes2Hex(b)
|
hex := Bytes2Hex(b)
|
||||||
|
|
@ -35,12 +33,11 @@ func FromHex(s string) []byte {
|
||||||
if s[0:2] == "0x" || s[0:2] == "0X" {
|
if s[0:2] == "0x" || s[0:2] == "0X" {
|
||||||
s = s[2:]
|
s = s[2:]
|
||||||
}
|
}
|
||||||
if len(s)%2 == 1 {
|
|
||||||
s = "0" + s
|
|
||||||
}
|
|
||||||
return Hex2Bytes(s)
|
|
||||||
}
|
}
|
||||||
return nil
|
if len(s)%2 == 1 {
|
||||||
|
s = "0" + s
|
||||||
|
}
|
||||||
|
return Hex2Bytes(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy bytes
|
// Copy bytes
|
||||||
|
|
@ -56,14 +53,24 @@ func CopyBytes(b []byte) (copiedBytes []byte) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func HasHexPrefix(str string) bool {
|
func hasHexPrefix(str string) bool {
|
||||||
l := len(str)
|
return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')
|
||||||
return l >= 2 && str[0:2] == "0x"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsHex(str string) bool {
|
func isHexCharacter(c byte) bool {
|
||||||
l := len(str)
|
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
|
||||||
return l >= 4 && l%2 == 0 && str[0:2] == "0x"
|
}
|
||||||
|
|
||||||
|
func isHex(str string) bool {
|
||||||
|
if len(str)%2 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range []byte(str) {
|
||||||
|
if !isHexCharacter(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func Bytes2Hex(d []byte) string {
|
func Bytes2Hex(d []byte) string {
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,6 @@ func (s *BytesSuite) TestCopyBytes(c *checker.C) {
|
||||||
c.Assert(res1, checker.DeepEquals, exp1)
|
c.Assert(res1, checker.DeepEquals, exp1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BytesSuite) TestIsHex(c *checker.C) {
|
|
||||||
data1 := "a9e67e"
|
|
||||||
exp1 := false
|
|
||||||
res1 := IsHex(data1)
|
|
||||||
c.Assert(res1, checker.DeepEquals, exp1)
|
|
||||||
|
|
||||||
data2 := "0xa9e67e00"
|
|
||||||
exp2 := true
|
|
||||||
res2 := IsHex(data2)
|
|
||||||
c.Assert(res2, checker.DeepEquals, exp2)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BytesSuite) TestLeftPadBytes(c *checker.C) {
|
func (s *BytesSuite) TestLeftPadBytes(c *checker.C) {
|
||||||
val1 := []byte{1, 2, 3, 4}
|
val1 := []byte{1, 2, 3, 4}
|
||||||
exp1 := []byte{0, 0, 0, 0, 1, 2, 3, 4}
|
exp1 := []byte{0, 0, 0, 0, 1, 2, 3, 4}
|
||||||
|
|
@ -74,7 +61,28 @@ func TestFromHex(t *testing.T) {
|
||||||
expected := []byte{1}
|
expected := []byte{1}
|
||||||
result := FromHex(input)
|
result := FromHex(input)
|
||||||
if !bytes.Equal(expected, result) {
|
if !bytes.Equal(expected, result) {
|
||||||
t.Errorf("Expected % x got % x", expected, result)
|
t.Errorf("Expected %x got %x", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsHex(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{"", true},
|
||||||
|
{"0", false},
|
||||||
|
{"00", true},
|
||||||
|
{"a9e67e", true},
|
||||||
|
{"A9E67E", true},
|
||||||
|
{"0xa9e67e", false},
|
||||||
|
{"a9e67e001", false},
|
||||||
|
{"0xHELLO_MY_NAME_IS_STEVEN_@#$^&*", false},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
if ok := isHex(test.input); ok != test.ok {
|
||||||
|
t.Errorf("isHex(%q) = %v, want %v", test.input, ok, test.ok)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,6 +91,15 @@ func TestFromHexOddLength(t *testing.T) {
|
||||||
expected := []byte{1}
|
expected := []byte{1}
|
||||||
result := FromHex(input)
|
result := FromHex(input)
|
||||||
if !bytes.Equal(expected, result) {
|
if !bytes.Equal(expected, result) {
|
||||||
t.Errorf("Expected % x got % x", expected, result)
|
t.Errorf("Expected %x got %x", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoPrefixShortHexOddLength(t *testing.T) {
|
||||||
|
input := "1"
|
||||||
|
expected := []byte{1}
|
||||||
|
result := FromHex(input)
|
||||||
|
if !bytes.Equal(expected, result) {
|
||||||
|
t.Errorf("Expected %x got %x", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,13 +150,10 @@ func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
|
||||||
// IsHexAddress verifies whether a string can represent a valid hex-encoded
|
// IsHexAddress verifies whether a string can represent a valid hex-encoded
|
||||||
// Ethereum address or not.
|
// Ethereum address or not.
|
||||||
func IsHexAddress(s string) bool {
|
func IsHexAddress(s string) bool {
|
||||||
if len(s) == 2+2*AddressLength && IsHex(s) {
|
if hasHexPrefix(s) {
|
||||||
return true
|
s = s[2:]
|
||||||
}
|
}
|
||||||
if len(s) == 2*AddressLength && IsHex("0x"+s) {
|
return len(s) == 2*AddressLength && isHex(s)
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the string representation of the underlying address
|
// Get the string representation of the underlying address
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,30 @@ func TestBytesConversion(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsHexAddress(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
str string
|
||||||
|
exp bool
|
||||||
|
}{
|
||||||
|
{"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", true},
|
||||||
|
{"5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", true},
|
||||||
|
{"0X5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", true},
|
||||||
|
{"0XAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", true},
|
||||||
|
{"0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", true},
|
||||||
|
{"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed1", false},
|
||||||
|
{"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beae", false},
|
||||||
|
{"5aaeb6053f3e94c9b9a09f33669435e7ef1beaed11", false},
|
||||||
|
{"0xxaaeb6053f3e94c9b9a09f33669435e7ef1beaed", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
if result := IsHexAddress(test.str); result != test.exp {
|
||||||
|
t.Errorf("IsHexAddress(%s) == %v; expected %v",
|
||||||
|
test.str, result, test.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHashJsonValidation(t *testing.T) {
|
func TestHashJsonValidation(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
Prefix string
|
Prefix string
|
||||||
|
|
|
||||||
|
|
@ -510,7 +510,6 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
|
||||||
header.Nonce = types.BlockNonce{}
|
header.Nonce = types.BlockNonce{}
|
||||||
|
|
||||||
number := header.Number.Uint64()
|
number := header.Number.Uint64()
|
||||||
|
|
||||||
// Assemble the voting snapshot to check which votes make sense
|
// Assemble the voting snapshot to check which votes make sense
|
||||||
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
|
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -538,10 +537,8 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
|
||||||
c.lock.RUnlock()
|
c.lock.RUnlock()
|
||||||
}
|
}
|
||||||
// Set the correct difficulty
|
// Set the correct difficulty
|
||||||
header.Difficulty = diffNoTurn
|
header.Difficulty = CalcDifficulty(snap, c.signer)
|
||||||
if snap.inturn(header.Number.Uint64(), c.signer) {
|
|
||||||
header.Difficulty = diffInTurn
|
|
||||||
}
|
|
||||||
// Ensure the extra data has all it's components
|
// Ensure the extra data has all it's components
|
||||||
if len(header.Extra) < extraVanity {
|
if len(header.Extra) < extraVanity {
|
||||||
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
|
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
|
||||||
|
|
@ -630,7 +627,7 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Sweet, the protocol permits us to sign the block, wait for our time
|
// Sweet, the protocol permits us to sign the block, wait for our time
|
||||||
delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now())
|
delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple
|
||||||
if header.Difficulty.Cmp(diffNoTurn) == 0 {
|
if header.Difficulty.Cmp(diffNoTurn) == 0 {
|
||||||
// It's not our turn explicitly to sign, delay it a bit
|
// It's not our turn explicitly to sign, delay it a bit
|
||||||
wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
|
wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
|
||||||
|
|
@ -655,6 +652,27 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
|
||||||
return block.WithSeal(header), nil
|
return block.WithSeal(header), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||||
|
// that a new block should have based on the previous blocks in the chain and the
|
||||||
|
// current signer.
|
||||||
|
func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||||
|
snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return CalcDifficulty(snap, c.signer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||||
|
// that a new block should have based on the previous blocks in the chain and the
|
||||||
|
// current signer.
|
||||||
|
func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
|
||||||
|
if snap.inturn(snap.Number+1, signer) {
|
||||||
|
return new(big.Int).Set(diffInTurn)
|
||||||
|
}
|
||||||
|
return new(big.Int).Set(diffNoTurn)
|
||||||
|
}
|
||||||
|
|
||||||
// APIs implements consensus.Engine, returning the user facing RPC API to allow
|
// APIs implements consensus.Engine, returning the user facing RPC API to allow
|
||||||
// controlling the signer voting.
|
// controlling the signer voting.
|
||||||
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
|
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ChainReader defines a small collection of methods needed to access the local
|
// ChainReader defines a small collection of methods needed to access the local
|
||||||
|
|
@ -88,6 +89,10 @@ type Engine interface {
|
||||||
// seal place on top.
|
// seal place on top.
|
||||||
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error)
|
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error)
|
||||||
|
|
||||||
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||||
|
// that a new block should have.
|
||||||
|
CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
|
||||||
|
|
||||||
// APIs returns the RPC APIs this consensus engine provides.
|
// APIs returns the RPC APIs this consensus engine provides.
|
||||||
APIs(chain ChainReader) []rpc.API
|
APIs(chain ChainReader) []rpc.API
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,10 @@ import (
|
||||||
|
|
||||||
// Ethash proof-of-work protocol constants.
|
// Ethash proof-of-work protocol constants.
|
||||||
var (
|
var (
|
||||||
FrontierBlockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
|
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(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
|
||||||
maxUncles = 2 // Maximum number of uncles allowed in a single block
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
// Various error messages to mark blocks invalid. These should be private to
|
// Various error messages to mark blocks invalid. These should be private to
|
||||||
|
|
@ -231,7 +232,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
|
||||||
return errLargeBlockTime
|
return errLargeBlockTime
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 {
|
if header.Time.Cmp(big.NewInt(time.Now().Add(allowedFutureBlockTime).Unix())) > 0 {
|
||||||
return consensus.ErrFutureBlock
|
return consensus.ErrFutureBlock
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +240,8 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
|
||||||
return errZeroBlockTime
|
return errZeroBlockTime
|
||||||
}
|
}
|
||||||
// Verify the block's difficulty based in it's timestamp and parent's difficulty
|
// Verify the block's difficulty based in it's timestamp and parent's difficulty
|
||||||
expected := CalcDifficulty(chain.Config(), header.Time.Uint64(), parent)
|
expected := ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)
|
||||||
|
|
||||||
if expected.Cmp(header.Difficulty) != 0 {
|
if expected.Cmp(header.Difficulty) != 0 {
|
||||||
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
|
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +288,13 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
|
||||||
// CalcDifficulty is the difficulty adjustment algorithm. It returns
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns
|
||||||
// the difficulty that a new block should have when created at time
|
// the difficulty that a new block should have when created at time
|
||||||
// given the parent block's time and difficulty.
|
// given the parent block's time and difficulty.
|
||||||
// TODO (karalabe): Move the chain maker into this package and make this private!
|
func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||||
|
return CalcDifficulty(chain.Config(), time, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns
|
||||||
|
// the difficulty that a new block should have when created at time
|
||||||
|
// given the parent block's time and difficulty.
|
||||||
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
|
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
|
||||||
next := new(big.Int).Add(parent.Number, big1)
|
next := new(big.Int).Add(parent.Number, big1)
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -501,8 +509,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
|
||||||
if parent == nil {
|
if parent == nil {
|
||||||
return consensus.ErrUnknownAncestor
|
return consensus.ErrUnknownAncestor
|
||||||
}
|
}
|
||||||
header.Difficulty = CalcDifficulty(chain.Config(), header.Time.Uint64(), parent)
|
header.Difficulty = ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,7 +517,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
|
||||||
// setting the final state and assembling the block.
|
// setting the final state and assembling the block.
|
||||||
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||||
// Accumulate any block and uncle rewards and commit the final state root
|
// Accumulate any block and uncle rewards and commit the final state root
|
||||||
AccumulateRewards(chain.Config(), state, header, uncles)
|
accumulateRewards(chain.Config(), state, header, uncles)
|
||||||
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||||
|
|
||||||
// Header seems complete, assemble into a block and return
|
// Header seems complete, assemble into a block and return
|
||||||
|
|
@ -526,8 +533,7 @@ var (
|
||||||
// AccumulateRewards credits the coinbase of the given block with the mining
|
// AccumulateRewards credits the coinbase of the given block with the mining
|
||||||
// reward. The total reward consists of the static block reward and rewards for
|
// reward. The total reward consists of the static block reward and rewards for
|
||||||
// included uncles. The coinbase of each uncle block is also rewarded.
|
// included uncles. The coinbase of each uncle block is also rewarded.
|
||||||
// TODO (karalabe): Move the chain maker into this package and make this private!
|
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
|
||||||
func AccumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
|
|
||||||
// Select the correct block reward based on chain progression
|
// Select the correct block reward based on chain progression
|
||||||
blockReward := FrontierBlockReward
|
blockReward := FrontierBlockReward
|
||||||
if config.IsByzantium(header.Number) {
|
if config.IsByzantium(header.Number) {
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ func TestCalcDifficulty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)}
|
config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
|
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
|
||||||
diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{
|
diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,9 @@ func New(config Config) (*Console, error) {
|
||||||
printer: config.Printer,
|
printer: config.Printer,
|
||||||
histPath: filepath.Join(config.DataDir, HistoryFile),
|
histPath: filepath.Join(config.DataDir, HistoryFile),
|
||||||
}
|
}
|
||||||
|
if err := os.MkdirAll(config.DataDir, 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := console.init(config.Preload); err != nil {
|
if err := console.init(config.Preload); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -192,6 +195,7 @@ func (c *Console) init(preload []string) error {
|
||||||
if obj := admin.Object(); obj != nil { // make sure the admin api is enabled over the interface
|
if obj := admin.Object(); obj != nil { // make sure the admin api is enabled over the interface
|
||||||
obj.Set("sleepBlocks", bridge.SleepBlocks)
|
obj.Set("sleepBlocks", bridge.SleepBlocks)
|
||||||
obj.Set("sleep", bridge.Sleep)
|
obj.Set("sleep", bridge.Sleep)
|
||||||
|
obj.Set("clearHistory", c.clearHistory)
|
||||||
}
|
}
|
||||||
// Preload any JavaScript files before starting the console
|
// Preload any JavaScript files before starting the console
|
||||||
for _, path := range preload {
|
for _, path := range preload {
|
||||||
|
|
@ -216,6 +220,16 @@ func (c *Console) init(preload []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Console) clearHistory() {
|
||||||
|
c.history = nil
|
||||||
|
c.prompter.ClearHistory()
|
||||||
|
if err := os.Remove(c.histPath); err != nil {
|
||||||
|
fmt.Fprintln(c.printer, "can't delete history file:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintln(c.printer, "history file deleted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// consoleOutput is an override for the console.log and console.error methods to
|
// consoleOutput is an override for the console.log and console.error methods to
|
||||||
// stream the output into the configured output stream instead of stdout.
|
// stream the output into the configured output stream instead of stdout.
|
||||||
func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
|
func (c *Console) consoleOutput(call otto.FunctionCall) otto.Value {
|
||||||
|
|
@ -412,7 +426,7 @@ func (c *Console) Execute(path string) error {
|
||||||
return c.jsre.Exec(path)
|
return c.jsre.Exec(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop cleans up the console and terminates the runtime envorinment.
|
// Stop cleans up the console and terminates the runtime environment.
|
||||||
func (c *Console) Stop(graceful bool) error {
|
func (c *Console) Stop(graceful bool) error {
|
||||||
if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
|
if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) {
|
||||||
}
|
}
|
||||||
func (p *hookedPrompter) SetHistory(history []string) {}
|
func (p *hookedPrompter) SetHistory(history []string) {}
|
||||||
func (p *hookedPrompter) AppendHistory(command string) {}
|
func (p *hookedPrompter) AppendHistory(command string) {}
|
||||||
|
func (p *hookedPrompter) ClearHistory() {}
|
||||||
func (p *hookedPrompter) SetWordCompleter(completer WordCompleter) {}
|
func (p *hookedPrompter) SetWordCompleter(completer WordCompleter) {}
|
||||||
|
|
||||||
// tester is a console test environment for the console tests to operate on.
|
// tester is a console test environment for the console tests to operate on.
|
||||||
|
|
@ -163,7 +164,7 @@ func TestWelcome(t *testing.T) {
|
||||||
|
|
||||||
tester.console.Welcome()
|
tester.console.Welcome()
|
||||||
|
|
||||||
output := string(tester.output.Bytes())
|
output := tester.output.String()
|
||||||
if want := "Welcome"; !strings.Contains(output, want) {
|
if want := "Welcome"; !strings.Contains(output, want) {
|
||||||
t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want)
|
t.Fatalf("console output missing welcome message: have\n%s\nwant also %s", output, want)
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +188,7 @@ func TestEvaluate(t *testing.T) {
|
||||||
defer tester.Close(t)
|
defer tester.Close(t)
|
||||||
|
|
||||||
tester.console.Evaluate("2 + 2")
|
tester.console.Evaluate("2 + 2")
|
||||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "4") {
|
if output := tester.output.String(); !strings.Contains(output, "4") {
|
||||||
t.Fatalf("statement evaluation failed: have %s, want %s", output, "4")
|
t.Fatalf("statement evaluation failed: have %s, want %s", output, "4")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,7 +218,7 @@ func TestInteractive(t *testing.T) {
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatalf("secondary prompt timeout")
|
t.Fatalf("secondary prompt timeout")
|
||||||
}
|
}
|
||||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "4") {
|
if output := tester.output.String(); !strings.Contains(output, "4") {
|
||||||
t.Fatalf("statement evaluation failed: have %s, want %s", output, "4")
|
t.Fatalf("statement evaluation failed: have %s, want %s", output, "4")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +230,7 @@ func TestPreload(t *testing.T) {
|
||||||
defer tester.Close(t)
|
defer tester.Close(t)
|
||||||
|
|
||||||
tester.console.Evaluate("preloaded")
|
tester.console.Evaluate("preloaded")
|
||||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "some-preloaded-string") {
|
if output := tester.output.String(); !strings.Contains(output, "some-preloaded-string") {
|
||||||
t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string")
|
t.Fatalf("preloaded variable missing: have %s, want %s", output, "some-preloaded-string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +243,7 @@ func TestExecute(t *testing.T) {
|
||||||
tester.console.Execute("exec.js")
|
tester.console.Execute("exec.js")
|
||||||
|
|
||||||
tester.console.Evaluate("execed")
|
tester.console.Evaluate("execed")
|
||||||
if output := string(tester.output.Bytes()); !strings.Contains(output, "some-executed-string") {
|
if output := tester.output.String(); !strings.Contains(output, "some-executed-string") {
|
||||||
t.Fatalf("execed variable missing: have %s, want %s", output, "some-executed-string")
|
t.Fatalf("execed variable missing: have %s, want %s", output, "some-executed-string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,7 +275,7 @@ func TestPrettyPrint(t *testing.T) {
|
||||||
string: ` + two + `
|
string: ` + two + `
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
if output := string(tester.output.Bytes()); output != want {
|
if output := tester.output.String(); output != want {
|
||||||
t.Fatalf("pretty print mismatch: have %s, want %s", output, want)
|
t.Fatalf("pretty print mismatch: have %s, want %s", output, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +287,7 @@ func TestPrettyError(t *testing.T) {
|
||||||
tester.console.Evaluate("throw 'hello'")
|
tester.console.Evaluate("throw 'hello'")
|
||||||
|
|
||||||
want := jsre.ErrorColor("hello") + "\n"
|
want := jsre.ErrorColor("hello") + "\n"
|
||||||
if output := string(tester.output.Bytes()); output != want {
|
if output := tester.output.String(); output != want {
|
||||||
t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
|
t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,9 @@ type UserPrompter interface {
|
||||||
// if and only if the prompt to append was a valid command.
|
// if and only if the prompt to append was a valid command.
|
||||||
AppendHistory(command string)
|
AppendHistory(command string)
|
||||||
|
|
||||||
|
// ClearHistory clears the entire history
|
||||||
|
ClearHistory()
|
||||||
|
|
||||||
// SetWordCompleter sets the completion function that the prompter will call to
|
// SetWordCompleter sets the completion function that the prompter will call to
|
||||||
// fetch completion candidates when the user presses tab.
|
// fetch completion candidates when the user presses tab.
|
||||||
SetWordCompleter(completer WordCompleter)
|
SetWordCompleter(completer WordCompleter)
|
||||||
|
|
@ -158,6 +161,11 @@ func (p *terminalPrompter) AppendHistory(command string) {
|
||||||
p.State.AppendHistory(command)
|
p.State.AppendHistory(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearHistory clears the entire history
|
||||||
|
func (p *terminalPrompter) ClearHistory() {
|
||||||
|
p.State.ClearHistory()
|
||||||
|
}
|
||||||
|
|
||||||
// SetWordCompleter sets the completion function that the prompter will call to
|
// SetWordCompleter sets the completion function that the prompter will call to
|
||||||
// fetch completion candidates when the user presses tab.
|
// fetch completion candidates when the user presses tab.
|
||||||
func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) {
|
func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) {
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ func (r *ReleaseService) checkVersion() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == bind.ErrNoCode {
|
if err == bind.ErrNoCode {
|
||||||
log.Debug("Release oracle not found", "contract", r.config.Oracle)
|
log.Debug("Release oracle not found", "contract", r.config.Oracle)
|
||||||
} else {
|
} else if err != les.ErrNoPeers {
|
||||||
log.Error("Failed to retrieve current release", "err", err)
|
log.Error("Failed to retrieve current release", "err", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -114,10 +114,7 @@ func PrintDisassembled(code string) error {
|
||||||
fmt.Printf("%06v: %v\n", it.PC(), it.Op())
|
fmt.Printf("%06v: %v\n", it.PC(), it.Op())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := it.Error(); err != nil {
|
return it.Error()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return all disassembled EVM instructions in human-readable format.
|
// Return all disassembled EVM instructions in human-readable format.
|
||||||
|
|
|
||||||
|
|
@ -237,10 +237,7 @@ func (c *Compiler) pushBin(v interface{}) {
|
||||||
// isPush returns whether the string op is either any of
|
// isPush returns whether the string op is either any of
|
||||||
// push(N).
|
// push(N).
|
||||||
func isPush(op string) bool {
|
func isPush(op string) bool {
|
||||||
if op == "push" {
|
return op == "push"
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// isJump returns whether the string op is jump(i)
|
// isJump returns whether the string op is jump(i)
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
|
||||||
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
|
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
|
||||||
}
|
}
|
||||||
genesis := gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, b.N, gen)
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, b.N, gen)
|
||||||
|
|
||||||
// Time the insertion of the new chain.
|
// Time the insertion of the new chain.
|
||||||
// State and blocks are stored in the same DB.
|
// State and blocks are stored in the same DB.
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func TestHeaderVerification(t *testing.T) {
|
||||||
testdb, _ = ethdb.NewMemDatabase()
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
gspec = &Genesis{Config: params.TestChainConfig}
|
gspec = &Genesis{Config: params.TestChainConfig}
|
||||||
genesis = gspec.MustCommit(testdb)
|
genesis = gspec.MustCommit(testdb)
|
||||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil)
|
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||||
)
|
)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
for i, block := range blocks {
|
for i, block := range blocks {
|
||||||
|
|
@ -87,7 +87,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
|
||||||
testdb, _ = ethdb.NewMemDatabase()
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
gspec = &Genesis{Config: params.TestChainConfig}
|
gspec = &Genesis{Config: params.TestChainConfig}
|
||||||
genesis = gspec.MustCommit(testdb)
|
genesis = gspec.MustCommit(testdb)
|
||||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil)
|
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||||
)
|
)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
seals := make([]bool, len(blocks))
|
seals := make([]bool, len(blocks))
|
||||||
|
|
@ -159,7 +159,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
|
||||||
testdb, _ = ethdb.NewMemDatabase()
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
gspec = &Genesis{Config: params.TestChainConfig}
|
gspec = &Genesis{Config: params.TestChainConfig}
|
||||||
genesis = gspec.MustCommit(testdb)
|
genesis = gspec.MustCommit(testdb)
|
||||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 1024, nil)
|
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil)
|
||||||
)
|
)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
seals := make([]bool, len(blocks))
|
seals := make([]bool, len(blocks))
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func newTestBlockChain(fake bool) *BlockChain {
|
||||||
// Test fork of length N starting from block i
|
// Test fork of length N starting from block i
|
||||||
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
|
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
|
||||||
// Copy old chain up to #i into a new db
|
// Copy old chain up to #i into a new db
|
||||||
db, blockchain2, err := newCanonical(i, full)
|
db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not make new canonical in testFork", err)
|
t.Fatal("could not make new canonical in testFork", err)
|
||||||
}
|
}
|
||||||
|
|
@ -81,12 +81,12 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara
|
||||||
headerChainB []*types.Header
|
headerChainB []*types.Header
|
||||||
)
|
)
|
||||||
if full {
|
if full {
|
||||||
blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, db, forkSeed)
|
blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed)
|
||||||
if _, err := blockchain2.InsertChain(blockChainB); err != nil {
|
if _, err := blockchain2.InsertChain(blockChainB); err != nil {
|
||||||
t.Fatalf("failed to insert forking chain: %v", err)
|
t.Fatalf("failed to insert forking chain: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, db, forkSeed)
|
headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed)
|
||||||
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
|
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
|
||||||
t.Fatalf("failed to insert forking chain: %v", err)
|
t.Fatalf("failed to insert forking chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +186,7 @@ func TestLastBlock(t *testing.T) {
|
||||||
bchain := newTestBlockChain(false)
|
bchain := newTestBlockChain(false)
|
||||||
defer bchain.Stop()
|
defer bchain.Stop()
|
||||||
|
|
||||||
block := makeBlockChain(bchain.CurrentBlock(), 1, bchain.chainDb, 0)[0]
|
block := makeBlockChain(bchain.CurrentBlock(), 1, ethash.NewFaker(), bchain.chainDb, 0)[0]
|
||||||
bchain.insert(block)
|
bchain.insert(block)
|
||||||
if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
|
if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
|
||||||
t.Errorf("Write/Get HeadBlockHash failed")
|
t.Errorf("Write/Get HeadBlockHash failed")
|
||||||
|
|
@ -202,7 +202,7 @@ func testExtendCanonical(t *testing.T, full bool) {
|
||||||
length := 5
|
length := 5
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +230,7 @@ func testShorterFork(t *testing.T, full bool) {
|
||||||
length := 10
|
length := 10
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +260,7 @@ func testLongerFork(t *testing.T, full bool) {
|
||||||
length := 10
|
length := 10
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +290,7 @@ func testEqualFork(t *testing.T, full bool) {
|
||||||
length := 10
|
length := 10
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +317,7 @@ func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) }
|
||||||
|
|
||||||
func testBrokenChain(t *testing.T, full bool) {
|
func testBrokenChain(t *testing.T, full bool) {
|
||||||
// Make chain starting from genesis
|
// Make chain starting from genesis
|
||||||
db, blockchain, err := newCanonical(10, full)
|
db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -325,12 +325,12 @@ func testBrokenChain(t *testing.T, full bool) {
|
||||||
|
|
||||||
// Create a forked chain, and try to insert with a missing link
|
// Create a forked chain, and try to insert with a missing link
|
||||||
if full {
|
if full {
|
||||||
chain := makeBlockChain(blockchain.CurrentBlock(), 5, db, forkSeed)[1:]
|
chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed)[1:]
|
||||||
if err := testBlockChainImport(chain, blockchain); err == nil {
|
if err := testBlockChainImport(chain, blockchain); err == nil {
|
||||||
t.Errorf("broken block chain not reported")
|
t.Errorf("broken block chain not reported")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chain := makeHeaderChain(blockchain.CurrentHeader(), 5, db, forkSeed)[1:]
|
chain := makeHeaderChain(blockchain.CurrentHeader(), 5, ethash.NewFaker(), db, forkSeed)[1:]
|
||||||
if err := testHeaderChainImport(chain, blockchain); err == nil {
|
if err := testHeaderChainImport(chain, blockchain); err == nil {
|
||||||
t.Errorf("broken header chain not reported")
|
t.Errorf("broken header chain not reported")
|
||||||
}
|
}
|
||||||
|
|
@ -523,7 +523,7 @@ func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) }
|
||||||
func testInsertNonceError(t *testing.T, full bool) {
|
func testInsertNonceError(t *testing.T, full bool) {
|
||||||
for i := 1; i < 25 && !t.Failed(); i++ {
|
for i := 1; i < 25 && !t.Failed(); i++ {
|
||||||
// Create a pristine chain and database
|
// Create a pristine chain and database
|
||||||
db, blockchain, err := newCanonical(0, full)
|
db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create pristine chain: %v", err)
|
t.Fatalf("failed to create pristine chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -536,7 +536,7 @@ func testInsertNonceError(t *testing.T, full bool) {
|
||||||
failNum uint64
|
failNum uint64
|
||||||
)
|
)
|
||||||
if full {
|
if full {
|
||||||
blocks := makeBlockChain(blockchain.CurrentBlock(), i, db, 0)
|
blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0)
|
||||||
|
|
||||||
failAt = rand.Int() % len(blocks)
|
failAt = rand.Int() % len(blocks)
|
||||||
failNum = blocks[failAt].NumberU64()
|
failNum = blocks[failAt].NumberU64()
|
||||||
|
|
@ -544,7 +544,7 @@ func testInsertNonceError(t *testing.T, full bool) {
|
||||||
blockchain.engine = ethash.NewFakeFailer(failNum)
|
blockchain.engine = ethash.NewFakeFailer(failNum)
|
||||||
failRes, err = blockchain.InsertChain(blocks)
|
failRes, err = blockchain.InsertChain(blocks)
|
||||||
} else {
|
} else {
|
||||||
headers := makeHeaderChain(blockchain.CurrentHeader(), i, db, 0)
|
headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0)
|
||||||
|
|
||||||
failAt = rand.Int() % len(headers)
|
failAt = rand.Int() % len(headers)
|
||||||
failNum = headers[failAt].Number.Uint64()
|
failNum = headers[failAt].Number.Uint64()
|
||||||
|
|
@ -588,7 +588,7 @@ func TestFastVsFullChains(t *testing.T) {
|
||||||
genesis = gspec.MustCommit(gendb)
|
genesis = gspec.MustCommit(gendb)
|
||||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||||
)
|
)
|
||||||
blocks, receipts := GenerateChain(gspec.Config, genesis, gendb, 1024, func(i int, block *BlockGen) {
|
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) {
|
||||||
block.SetCoinbase(common.Address{0x00})
|
block.SetCoinbase(common.Address{0x00})
|
||||||
|
|
||||||
// If the block number is multiple of 3, send a few bonus transactions to the miner
|
// If the block number is multiple of 3, send a few bonus transactions to the miner
|
||||||
|
|
@ -673,7 +673,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||||
genesis = gspec.MustCommit(gendb)
|
genesis = gspec.MustCommit(gendb)
|
||||||
)
|
)
|
||||||
height := uint64(1024)
|
height := uint64(1024)
|
||||||
blocks, receipts := GenerateChain(gspec.Config, genesis, gendb, int(height), nil)
|
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), nil)
|
||||||
|
|
||||||
// Configure a subchain to roll back
|
// Configure a subchain to roll back
|
||||||
remove := []common.Hash{}
|
remove := []common.Hash{}
|
||||||
|
|
@ -781,7 +781,7 @@ func TestChainTxReorgs(t *testing.T) {
|
||||||
// - futureAdd: transaction added after the reorg has already finished
|
// - futureAdd: transaction added after the reorg has already finished
|
||||||
var pastAdd, freshAdd, futureAdd *types.Transaction
|
var pastAdd, freshAdd, futureAdd *types.Transaction
|
||||||
|
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, gen *BlockGen) {
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
|
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
|
||||||
|
|
@ -806,7 +806,7 @@ func TestChainTxReorgs(t *testing.T) {
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
// overwrite the old chain
|
// overwrite the old chain
|
||||||
chain, _ = GenerateChain(gspec.Config, genesis, db, 5, func(i int, gen *BlockGen) {
|
chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
||||||
|
|
@ -875,7 +875,7 @@ func TestLogReorgs(t *testing.T) {
|
||||||
|
|
||||||
rmLogsCh := make(chan RemovedLogsEvent)
|
rmLogsCh := make(chan RemovedLogsEvent)
|
||||||
blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
|
blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
|
||||||
chain, _ := GenerateChain(params.TestChainConfig, genesis, db, 2, func(i int, gen *BlockGen) {
|
chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
|
||||||
if i == 1 {
|
if i == 1 {
|
||||||
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
|
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -888,7 +888,7 @@ func TestLogReorgs(t *testing.T) {
|
||||||
t.Fatalf("failed to insert chain: %v", err)
|
t.Fatalf("failed to insert chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chain, _ = GenerateChain(params.TestChainConfig, genesis, db, 3, func(i int, gen *BlockGen) {})
|
chain, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
|
||||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
t.Fatalf("failed to insert forked chain: %v", err)
|
t.Fatalf("failed to insert forked chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -920,12 +920,12 @@ func TestReorgSideEvent(t *testing.T) {
|
||||||
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, gen *BlockGen) {})
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
|
||||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
t.Fatalf("failed to insert chain: %v", err)
|
t.Fatalf("failed to insert chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
replacementBlocks, _ := GenerateChain(gspec.Config, genesis, db, 4, func(i int, gen *BlockGen) {
|
replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) {
|
||||||
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1)
|
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1)
|
||||||
if i == 2 {
|
if i == 2 {
|
||||||
gen.OffsetTime(-9)
|
gen.OffsetTime(-9)
|
||||||
|
|
@ -992,7 +992,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
|
||||||
bc := newTestBlockChain(true)
|
bc := newTestBlockChain(true)
|
||||||
defer bc.Stop()
|
defer bc.Stop()
|
||||||
|
|
||||||
chain, _ := GenerateChain(bc.config, bc.genesisBlock, bc.chainDb, 10, func(i int, gen *BlockGen) {})
|
chain, _ := GenerateChain(bc.config, bc.genesisBlock, ethash.NewFaker(), bc.chainDb, 10, func(i int, gen *BlockGen) {})
|
||||||
|
|
||||||
var pend sync.WaitGroup
|
var pend sync.WaitGroup
|
||||||
pend.Add(len(chain))
|
pend.Add(len(chain))
|
||||||
|
|
@ -1046,7 +1046,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||||
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
blocks, _ := GenerateChain(gspec.Config, genesis, db, 4, func(i int, block *BlockGen) {
|
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
||||||
var (
|
var (
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
err error
|
err error
|
||||||
|
|
@ -1109,7 +1109,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||||
|
|
||||||
// generate an invalid chain id transaction
|
// generate an invalid chain id transaction
|
||||||
config := ¶ms.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
config := ¶ms.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
||||||
blocks, _ = GenerateChain(config, blocks[len(blocks)-1], db, 4, func(i int, block *BlockGen) {
|
blocks, _ = GenerateChain(config, blocks[len(blocks)-1], ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
||||||
var (
|
var (
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
err error
|
err error
|
||||||
|
|
@ -1154,7 +1154,7 @@ func TestEIP161AccountRemoval(t *testing.T) {
|
||||||
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
blocks, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, block *BlockGen) {
|
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, block *BlockGen) {
|
||||||
var (
|
var (
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -39,11 +39,12 @@ var (
|
||||||
// BlockGen creates blocks for testing.
|
// BlockGen creates blocks for testing.
|
||||||
// See GenerateChain for a detailed explanation.
|
// See GenerateChain for a detailed explanation.
|
||||||
type BlockGen struct {
|
type BlockGen struct {
|
||||||
i int
|
i int
|
||||||
parent *types.Block
|
parent *types.Block
|
||||||
chain []*types.Block
|
chain []*types.Block
|
||||||
header *types.Header
|
chainReader consensus.ChainReader
|
||||||
statedb *state.StateDB
|
header *types.Header
|
||||||
|
statedb *state.StateDB
|
||||||
|
|
||||||
gasPool *GasPool
|
gasPool *GasPool
|
||||||
txs []*types.Transaction
|
txs []*types.Transaction
|
||||||
|
|
@ -51,6 +52,7 @@ type BlockGen struct {
|
||||||
uncles []*types.Header
|
uncles []*types.Header
|
||||||
|
|
||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
|
engine consensus.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCoinbase sets the coinbase of the generated block.
|
// SetCoinbase sets the coinbase of the generated block.
|
||||||
|
|
@ -141,7 +143,7 @@ func (b *BlockGen) OffsetTime(seconds int64) {
|
||||||
if b.header.Time.Cmp(b.parent.Header().Time) <= 0 {
|
if b.header.Time.Cmp(b.parent.Header().Time) <= 0 {
|
||||||
panic("block time out of range")
|
panic("block time out of range")
|
||||||
}
|
}
|
||||||
b.header.Difficulty = ethash.CalcDifficulty(b.config, b.header.Time.Uint64(), b.parent.Header())
|
b.header.Difficulty = b.engine.CalcDifficulty(b.chainReader, b.header.Time.Uint64(), b.parent.Header())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateChain creates a chain of n blocks. The first block's
|
// GenerateChain creates a chain of n blocks. The first block's
|
||||||
|
|
@ -156,44 +158,54 @@ func (b *BlockGen) OffsetTime(seconds int64) {
|
||||||
// Blocks created by GenerateChain do not contain valid proof of work
|
// Blocks created by GenerateChain do not contain valid proof of work
|
||||||
// values. Inserting them into BlockChain requires use of FakePow or
|
// values. Inserting them into BlockChain requires use of FakePow or
|
||||||
// a similar non-validating proof of work implementation.
|
// a similar non-validating proof of work implementation.
|
||||||
func GenerateChain(config *params.ChainConfig, parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
|
func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
config = params.TestChainConfig
|
config = params.TestChainConfig
|
||||||
}
|
}
|
||||||
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
|
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
|
||||||
genblock := func(i int, h *types.Header, statedb *state.StateDB) (*types.Block, types.Receipts) {
|
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
|
||||||
b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb, config: config}
|
// TODO(karalabe): This is needed for clique, which depends on multiple blocks.
|
||||||
|
// It's nonetheless ugly to spin up a blockchain here. Get rid of this somehow.
|
||||||
|
blockchain, _ := NewBlockChain(db, config, engine, vm.Config{})
|
||||||
|
defer blockchain.Stop()
|
||||||
|
|
||||||
|
b := &BlockGen{i: i, parent: parent, chain: blocks, chainReader: blockchain, statedb: statedb, config: config, engine: engine}
|
||||||
|
b.header = makeHeader(b.chainReader, parent, statedb, b.engine)
|
||||||
|
|
||||||
// Mutate the state and block according to any hard-fork specs
|
// Mutate the state and block according to any hard-fork specs
|
||||||
if daoBlock := config.DAOForkBlock; daoBlock != nil {
|
if daoBlock := config.DAOForkBlock; daoBlock != nil {
|
||||||
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
|
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
|
||||||
if h.Number.Cmp(daoBlock) >= 0 && h.Number.Cmp(limit) < 0 {
|
if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 {
|
||||||
if config.DAOForkSupport {
|
if config.DAOForkSupport {
|
||||||
h.Extra = common.CopyBytes(params.DAOForkBlockExtra)
|
b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(h.Number) == 0 {
|
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
|
||||||
misc.ApplyDAOHardFork(statedb)
|
misc.ApplyDAOHardFork(statedb)
|
||||||
}
|
}
|
||||||
// Execute any user modifications to the block and finalize it
|
// Execute any user modifications to the block and finalize it
|
||||||
if gen != nil {
|
if gen != nil {
|
||||||
gen(i, b)
|
gen(i, b)
|
||||||
}
|
}
|
||||||
ethash.AccumulateRewards(config, statedb, h, b.uncles)
|
|
||||||
root, err := statedb.CommitTo(db, config.IsEIP158(h.Number))
|
if b.engine != nil {
|
||||||
if err != nil {
|
block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
|
||||||
panic(fmt.Sprintf("state write error: %v", err))
|
// Write state changes to db
|
||||||
|
_, err := statedb.CommitTo(db, config.IsEIP158(b.header.Number))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("state write error: %v", err))
|
||||||
|
}
|
||||||
|
return block, b.receipts
|
||||||
}
|
}
|
||||||
h.Root = root
|
return nil, nil
|
||||||
return types.NewBlock(h, b.txs, b.uncles, b.receipts), b.receipts
|
|
||||||
}
|
}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
statedb, err := state.New(parent.Root(), state.NewDatabase(db))
|
statedb, err := state.New(parent.Root(), state.NewDatabase(db))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
header := makeHeader(config, parent, statedb)
|
block, receipt := genblock(i, parent, statedb)
|
||||||
block, receipt := genblock(i, header, statedb)
|
|
||||||
blocks[i] = block
|
blocks[i] = block
|
||||||
receipts[i] = receipt
|
receipts[i] = receipt
|
||||||
parent = block
|
parent = block
|
||||||
|
|
@ -201,7 +213,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, db ethdb.Dat
|
||||||
return blocks, receipts
|
return blocks, receipts
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.StateDB) *types.Header {
|
func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
|
||||||
var time *big.Int
|
var time *big.Int
|
||||||
if parent.Time() == nil {
|
if parent.Time() == nil {
|
||||||
time = big.NewInt(10)
|
time = big.NewInt(10)
|
||||||
|
|
@ -210,10 +222,10 @@ func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.St
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.Header{
|
return &types.Header{
|
||||||
Root: state.IntermediateRoot(config.IsEIP158(parent.Number())),
|
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
|
||||||
ParentHash: parent.Hash(),
|
ParentHash: parent.Hash(),
|
||||||
Coinbase: parent.Coinbase(),
|
Coinbase: parent.Coinbase(),
|
||||||
Difficulty: ethash.CalcDifficulty(config, time.Uint64(), &types.Header{
|
Difficulty: engine.CalcDifficulty(chain, time.Uint64(), &types.Header{
|
||||||
Number: parent.Number(),
|
Number: parent.Number(),
|
||||||
Time: new(big.Int).Sub(time, big.NewInt(10)),
|
Time: new(big.Int).Sub(time, big.NewInt(10)),
|
||||||
Difficulty: parent.Difficulty(),
|
Difficulty: parent.Difficulty(),
|
||||||
|
|
@ -229,32 +241,32 @@ func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.St
|
||||||
// newCanonical creates a chain database, and injects a deterministic canonical
|
// newCanonical creates a chain database, and injects a deterministic canonical
|
||||||
// chain. Depending on the full flag, if creates either a full block chain or a
|
// chain. Depending on the full flag, if creates either a full block chain or a
|
||||||
// header only chain.
|
// header only chain.
|
||||||
func newCanonical(n int, full bool) (ethdb.Database, *BlockChain, error) {
|
func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) {
|
||||||
// Initialize a fresh chain with only a genesis block
|
// Initialize a fresh chain with only a genesis block
|
||||||
gspec := new(Genesis)
|
gspec := new(Genesis)
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
genesis := gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
|
|
||||||
blockchain, _ := NewBlockChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, params.AllEthashProtocolChanges, engine, vm.Config{})
|
||||||
// Create and inject the requested chain
|
// Create and inject the requested chain
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return db, blockchain, nil
|
return db, blockchain, nil
|
||||||
}
|
}
|
||||||
if full {
|
if full {
|
||||||
// Full block-chain requested
|
// Full block-chain requested
|
||||||
blocks := makeBlockChain(genesis, n, db, canonicalSeed)
|
blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
|
||||||
_, err := blockchain.InsertChain(blocks)
|
_, err := blockchain.InsertChain(blocks)
|
||||||
return db, blockchain, err
|
return db, blockchain, err
|
||||||
}
|
}
|
||||||
// Header-only chain requested
|
// Header-only chain requested
|
||||||
headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed)
|
headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
|
||||||
_, err := blockchain.InsertHeaderChain(headers, 1)
|
_, err := blockchain.InsertHeaderChain(headers, 1)
|
||||||
return db, blockchain, err
|
return db, blockchain, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
|
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
|
||||||
func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header {
|
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
|
||||||
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, db, seed)
|
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
for i, block := range blocks {
|
for i, block := range blocks {
|
||||||
headers[i] = block.Header()
|
headers[i] = block.Header()
|
||||||
|
|
@ -263,8 +275,8 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeBlockChain creates a deterministic chain of blocks rooted at parent.
|
// makeBlockChain creates a deterministic chain of blocks rooted at parent.
|
||||||
func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block {
|
func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block {
|
||||||
blocks, _ := GenerateChain(params.TestChainConfig, parent, db, n, func(i int, b *BlockGen) {
|
blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
|
||||||
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
|
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
|
||||||
})
|
})
|
||||||
return blocks
|
return blocks
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func ExampleGenerateChain() {
|
||||||
// each block and adds different features to gen based on the
|
// each block and adds different features to gen based on the
|
||||||
// block index.
|
// block index.
|
||||||
signer := types.HomesteadSigner{}
|
signer := types.HomesteadSigner{}
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, 5, func(i int, gen *BlockGen) {
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
// In block 1, addr1 sends addr2 some ether.
|
// In block 1, addr1 sends addr2 some ether.
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
gspec := new(Genesis)
|
gspec := new(Genesis)
|
||||||
genesis := gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
prefix, _ := GenerateChain(params.TestChainConfig, genesis, db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
||||||
|
|
||||||
// Create the concurrent, conflicting two nodes
|
// Create the concurrent, conflicting two nodes
|
||||||
proDb, _ := ethdb.NewMemDatabase()
|
proDb, _ := ethdb.NewMemDatabase()
|
||||||
|
|
@ -79,12 +79,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := conBc.InsertChain(blocks); err == nil {
|
if _, err := conBc.InsertChain(blocks); err == nil {
|
||||||
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
|
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
|
||||||
}
|
}
|
||||||
// Create a proper no-fork block for the contra-forker
|
// Create a proper no-fork block for the contra-forker
|
||||||
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := conBc.InsertChain(blocks); err != nil {
|
if _, err := conBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
|
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -101,12 +101,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := proBc.InsertChain(blocks); err == nil {
|
if _, err := proBc.InsertChain(blocks); err == nil {
|
||||||
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
|
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
|
||||||
}
|
}
|
||||||
// Create a proper pro-fork block for the pro-forker
|
// Create a proper pro-fork block for the pro-forker
|
||||||
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := proBc.InsertChain(blocks); err != nil {
|
if _, err := proBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
|
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := conBc.InsertChain(blocks); err != nil {
|
if _, err := conBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
|
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +141,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := proBc.InsertChain(blocks); err != nil {
|
if _, err := proBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
|
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -103,7 +103,7 @@ var (
|
||||||
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TxStatus is the current status of a transaction as seen py the pool.
|
// TxStatus is the current status of a transaction as seen by the pool.
|
||||||
type TxStatus uint
|
type TxStatus uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -199,7 +199,7 @@ type TxPool struct {
|
||||||
pendingState *state.ManagedState // Pending state tracking virtual nonces
|
pendingState *state.ManagedState // Pending state tracking virtual nonces
|
||||||
currentMaxGas *big.Int // Current gas limit for transaction caps
|
currentMaxGas *big.Int // Current gas limit for transaction caps
|
||||||
|
|
||||||
locals *accountSet // Set of local transaction to exepmt from evicion rules
|
locals *accountSet // Set of local transaction to exempt from eviction rules
|
||||||
journal *txJournal // Journal of local transaction to back up to disk
|
journal *txJournal // Journal of local transaction to back up to disk
|
||||||
|
|
||||||
pending map[common.Address]*txList // All currently processable transactions
|
pending map[common.Address]*txList // All currently processable transactions
|
||||||
|
|
@ -214,7 +214,7 @@ type TxPool struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
||||||
// trnsactions from the network.
|
// transactions from the network.
|
||||||
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
||||||
// Sanitize the input to ensure no vulnerable gas prices are set
|
// Sanitize the input to ensure no vulnerable gas prices are set
|
||||||
config = (&config).sanitize()
|
config = (&config).sanitize()
|
||||||
|
|
@ -360,7 +360,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||||
newNum := newHead.Number.Uint64()
|
newNum := newHead.Number.Uint64()
|
||||||
|
|
||||||
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
||||||
log.Warn("Skipping deep transaction reorg", "depth", depth)
|
log.Debug("Skipping deep transaction reorg", "depth", depth)
|
||||||
} else {
|
} else {
|
||||||
// Reorg seems shallow enough to pull in all transactions into memory
|
// Reorg seems shallow enough to pull in all transactions into memory
|
||||||
var discarded, included types.Transactions
|
var discarded, included types.Transactions
|
||||||
|
|
@ -838,7 +838,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||||
for i, hash := range hashes {
|
for i, hash := range hashes {
|
||||||
if tx := pool.all[hash]; tx != nil {
|
if tx := pool.all[hash]; tx != nil {
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
if pool.pending[from].txs.items[tx.Nonce()] != nil {
|
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
|
||||||
status[i] = TxStatusPending
|
status[i] = TxStatusPending
|
||||||
} else {
|
} else {
|
||||||
status[i] = TxStatusQueued
|
status[i] = TxStatusQueued
|
||||||
|
|
|
||||||
|
|
@ -1563,6 +1563,63 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
pool.Stop()
|
pool.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTransactionStatusCheck tests that the pool can correctly retrieve the
|
||||||
|
// pending status of individual transactions.
|
||||||
|
func TestTransactionStatusCheck(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create the pool to test the status retrievals with
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
blockchain := &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
|
||||||
|
|
||||||
|
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||||
|
defer pool.Stop()
|
||||||
|
|
||||||
|
// Create the test accounts to check various transaction statuses with
|
||||||
|
keys := make([]*ecdsa.PrivateKey, 3)
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
keys[i], _ = crypto.GenerateKey()
|
||||||
|
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||||
|
}
|
||||||
|
// Generate and queue a batch of transactions, both pending and queued
|
||||||
|
txs := types.Transactions{}
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[0])) // Pending only
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[1])) // Pending and queued
|
||||||
|
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1]))
|
||||||
|
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[2])) // Queued only
|
||||||
|
|
||||||
|
// Import the transaction and ensure they are correctly added
|
||||||
|
pool.AddRemotes(txs)
|
||||||
|
|
||||||
|
pending, queued := pool.Stats()
|
||||||
|
if pending != 2 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||||
|
}
|
||||||
|
if queued != 2 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||||
|
}
|
||||||
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
|
}
|
||||||
|
// Retrieve the status of each transaction and validate them
|
||||||
|
hashes := make([]common.Hash, len(txs))
|
||||||
|
for i, tx := range txs {
|
||||||
|
hashes[i] = tx.Hash()
|
||||||
|
}
|
||||||
|
hashes = append(hashes, common.Hash{})
|
||||||
|
|
||||||
|
statuses := pool.Status(hashes)
|
||||||
|
expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown}
|
||||||
|
|
||||||
|
for i := 0; i < len(statuses); i++ {
|
||||||
|
if statuses[i] != expect[i] {
|
||||||
|
t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Benchmarks the speed of validating the contents of the pending queue of the
|
// Benchmarks the speed of validating the contents of the pending queue of the
|
||||||
// transaction pool.
|
// transaction pool.
|
||||||
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ func isProtectedV(V *big.Int) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeRLP implements rlp.Encoder
|
// EncodeRLP implements rlp.Encoder
|
||||||
func (tx *Transaction) EncodeRLP(w io.Writer) error {
|
func (tx *Transaction) EncodeRLP(w io.Writer) error {
|
||||||
return rlp.Encode(w, &tx.data)
|
return rlp.Encode(w, &tx.data)
|
||||||
}
|
}
|
||||||
|
|
@ -153,6 +153,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the web3 RPC transaction format.
|
||||||
func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
data := tx.data
|
data := tx.data
|
||||||
|
|
@ -168,8 +169,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
}
|
}
|
||||||
var V byte
|
var V byte
|
||||||
if isProtectedV(dec.V) {
|
if isProtectedV(dec.V) {
|
||||||
chainId := deriveChainId(dec.V).Uint64()
|
chainID := deriveChainId(dec.V).Uint64()
|
||||||
V = byte(dec.V.Uint64() - 35 - 2*chainId)
|
V = byte(dec.V.Uint64() - 35 - 2*chainID)
|
||||||
} else {
|
} else {
|
||||||
V = byte(dec.V.Uint64() - 27)
|
V = byte(dec.V.Uint64() - 27)
|
||||||
}
|
}
|
||||||
|
|
@ -192,10 +193,9 @@ func (tx *Transaction) CheckNonce() bool { return true }
|
||||||
func (tx *Transaction) To() *common.Address {
|
func (tx *Transaction) To() *common.Address {
|
||||||
if tx.data.Recipient == nil {
|
if tx.data.Recipient == nil {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
to := *tx.data.Recipient
|
|
||||||
return &to
|
|
||||||
}
|
}
|
||||||
|
to := *tx.data.Recipient
|
||||||
|
return &to
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash hashes the RLP encoding of tx.
|
// Hash hashes the RLP encoding of tx.
|
||||||
|
|
@ -315,22 +315,22 @@ func (tx *Transaction) String() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction slice type for basic sorting.
|
// Transactions is a Transaction slice type for basic sorting.
|
||||||
type Transactions []*Transaction
|
type Transactions []*Transaction
|
||||||
|
|
||||||
// Len returns the length of s
|
// Len returns the length of s.
|
||||||
func (s Transactions) Len() int { return len(s) }
|
func (s Transactions) Len() int { return len(s) }
|
||||||
|
|
||||||
// Swap swaps the i'th and the j'th element in s
|
// Swap swaps the i'th and the j'th element in s.
|
||||||
func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
// GetRlp implements Rlpable and returns the i'th element of s in rlp
|
// GetRlp implements Rlpable and returns the i'th element of s in rlp.
|
||||||
func (s Transactions) GetRlp(i int) []byte {
|
func (s Transactions) GetRlp(i int) []byte {
|
||||||
enc, _ := rlp.EncodeToBytes(s[i])
|
enc, _ := rlp.EncodeToBytes(s[i])
|
||||||
return enc
|
return enc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new set t which is the difference between a to b
|
// TxDifference returns a new set t which is the difference between a to b.
|
||||||
func TxDifference(a, b Transactions) (keep Transactions) {
|
func TxDifference(a, b Transactions) (keep Transactions) {
|
||||||
keep = make(Transactions, 0, len(a))
|
keep = make(Transactions, 0, len(a))
|
||||||
|
|
||||||
|
|
@ -378,7 +378,7 @@ func (s *TxByPrice) Pop() interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionsByPriceAndNonce represents a set of transactions that can return
|
// TransactionsByPriceAndNonce represents a set of transactions that can return
|
||||||
// transactions in a profit-maximising sorted order, while supporting removing
|
// transactions in a profit-maximizing sorted order, while supporting removing
|
||||||
// entire batches of transactions for non-executable accounts.
|
// entire batches of transactions for non-executable accounts.
|
||||||
type TransactionsByPriceAndNonce struct {
|
type TransactionsByPriceAndNonce struct {
|
||||||
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions
|
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package vm
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
@ -38,7 +39,7 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
|
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
|
||||||
func run(evm *EVM, snapshot int, contract *Contract, input []byte) ([]byte, error) {
|
func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
|
||||||
if contract.CodeAddr != nil {
|
if contract.CodeAddr != nil {
|
||||||
precompiles := PrecompiledContractsHomestead
|
precompiles := PrecompiledContractsHomestead
|
||||||
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
|
if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
|
||||||
|
|
@ -48,7 +49,7 @@ func run(evm *EVM, snapshot int, contract *Contract, input []byte) ([]byte, erro
|
||||||
return RunPrecompiledContract(p, input, contract)
|
return RunPrecompiledContract(p, input, contract)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return evm.interpreter.Run(snapshot, contract, input)
|
return evm.interpreter.Run(contract, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context provides the EVM with auxiliary information. Once provided
|
// Context provides the EVM with auxiliary information. Once provided
|
||||||
|
|
@ -165,13 +166,23 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
}
|
}
|
||||||
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
|
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
|
||||||
|
|
||||||
// initialise a new contract and set the code that is to be used by the
|
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||||
// E The contract is a scoped environment for this execution context
|
// The contract is a scoped environment for this execution context only.
|
||||||
// only.
|
|
||||||
contract := NewContract(caller, to, value, gas)
|
contract := NewContract(caller, to, value, gas)
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
|
|
||||||
ret, err = run(evm, snapshot, contract, input)
|
start := time.Now()
|
||||||
|
|
||||||
|
// Capture the tracer start/end events in debug mode
|
||||||
|
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||||
|
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
|
||||||
|
|
||||||
|
defer func() { // Lazy evaluation of the parameters
|
||||||
|
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
ret, err = run(evm, contract, input)
|
||||||
|
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
// when we're in homestead this also counts for code storage gas errors.
|
// when we're in homestead this also counts for code storage gas errors.
|
||||||
|
|
@ -215,7 +226,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
|
||||||
contract := NewContract(caller, to, value, gas)
|
contract := NewContract(caller, to, value, gas)
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
|
|
||||||
ret, err = run(evm, snapshot, contract, input)
|
ret, err = run(evm, contract, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != errExecutionReverted {
|
||||||
|
|
@ -248,7 +259,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
|
||||||
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
contract := NewContract(caller, to, nil, gas).AsDelegate()
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
|
|
||||||
ret, err = run(evm, snapshot, contract, input)
|
ret, err = run(evm, contract, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != errExecutionReverted {
|
||||||
|
|
@ -291,7 +302,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
// When an error was returned by the EVM or when setting the creation code
|
// When an error was returned by the EVM or when setting the creation code
|
||||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||||
// when we're in Homestead this also counts for code storage gas errors.
|
// when we're in Homestead this also counts for code storage gas errors.
|
||||||
ret, err = run(evm, snapshot, contract, input)
|
ret, err = run(evm, contract, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
evm.StateDB.RevertToSnapshot(snapshot)
|
evm.StateDB.RevertToSnapshot(snapshot)
|
||||||
if err != errExecutionReverted {
|
if err != errExecutionReverted {
|
||||||
|
|
@ -338,7 +349,14 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
||||||
return nil, contractAddr, gas, nil
|
return nil, contractAddr, gas, nil
|
||||||
}
|
}
|
||||||
ret, err = run(evm, snapshot, contract, nil)
|
|
||||||
|
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||||
|
evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)
|
||||||
|
}
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
ret, err = run(evm, contract, nil)
|
||||||
|
|
||||||
// check whether the max code size has been exceeded
|
// check whether the max code size has been exceeded
|
||||||
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
|
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
|
||||||
// if the contract creation ran successfully and no errors were returned
|
// if the contract creation ran successfully and no errors were returned
|
||||||
|
|
@ -367,6 +385,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
if maxCodeSizeExceeded && err == nil {
|
if maxCodeSizeExceeded && err == nil {
|
||||||
err = errMaxCodeSizeExceeded
|
err = errMaxCodeSizeExceeded
|
||||||
}
|
}
|
||||||
|
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||||
|
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
|
||||||
|
}
|
||||||
return ret, contractAddr, contract.Gas, err
|
return ret, contractAddr, contract.Gas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,9 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
|
||||||
// the return byte-slice and an error if one occurred.
|
// the return byte-slice and an error if one occurred.
|
||||||
//
|
//
|
||||||
// It's important to note that any errors returned by the interpreter should be
|
// It's important to note that any errors returned by the interpreter should be
|
||||||
// considered a revert-and-consume-all-gas operation. No error specific checks
|
// considered a revert-and-consume-all-gas operation except for
|
||||||
// should be handled to reduce complexity and errors further down the in.
|
// errExecutionReverted which means revert-and-keep-gas-left.
|
||||||
func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret []byte, err error) {
|
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||||
// Increment the call depth which is restricted to 1024
|
// Increment the call depth which is restricted to 1024
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
@ -144,12 +144,17 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
|
||||||
)
|
)
|
||||||
contract.Input = input
|
contract.Input = input
|
||||||
|
|
||||||
defer func() {
|
if in.cfg.Debug {
|
||||||
if err != nil && !logged && in.cfg.Debug {
|
defer func() {
|
||||||
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
if err != nil {
|
||||||
}
|
if !logged {
|
||||||
}()
|
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
||||||
|
} else {
|
||||||
|
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
// The Interpreter main run loop (contextual). This loop runs until either an
|
// The Interpreter main run loop (contextual). This loop runs until either an
|
||||||
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
||||||
// the execution of one of the operations or until the done flag is set by the
|
// the execution of one of the operations or until the done flag is set by the
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ func (s *StructLog) OpName() string {
|
||||||
// Note that reference types are actual VM data structures; make copies
|
// Note that reference types are actual VM data structures; make copies
|
||||||
// if you need to retain them beyond the current call.
|
// if you need to retain them beyond the current call.
|
||||||
type Tracer interface {
|
type Tracer interface {
|
||||||
|
CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) error
|
||||||
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
|
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
|
||||||
|
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
|
||||||
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
|
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +113,10 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CaptureState logs a new structured log message and pushes it out to the environment
|
// CaptureState logs a new structured log message and pushes it out to the environment
|
||||||
//
|
//
|
||||||
// CaptureState also tracks SSTORE ops to track dirty values.
|
// CaptureState also tracks SSTORE ops to track dirty values.
|
||||||
|
|
@ -161,6 +167,10 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||||
fmt.Printf("0x%x", output)
|
fmt.Printf("0x%x", output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ var curveB = new(big.Int).SetInt64(3)
|
||||||
// curveGen is the generator of G₁.
|
// curveGen is the generator of G₁.
|
||||||
var curveGen = &curvePoint{
|
var curveGen = &curvePoint{
|
||||||
new(big.Int).SetInt64(1),
|
new(big.Int).SetInt64(1),
|
||||||
new(big.Int).SetInt64(-2),
|
new(big.Int).SetInt64(2),
|
||||||
new(big.Int).SetInt64(1),
|
new(big.Int).SetInt64(1),
|
||||||
new(big.Int).SetInt64(1),
|
new(big.Int).SetInt64(1),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ func ToECDSA(d []byte) (*ecdsa.PrivateKey, error) {
|
||||||
return toECDSA(d, true)
|
return toECDSA(d, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToECDSAUnsafe blidly converts a binary blob to a private key. It should almost
|
// ToECDSAUnsafe blindly converts a binary blob to a private key. It should almost
|
||||||
// never be used unless you are sure the input is valid and want to avoid hitting
|
// never be used unless you are sure the input is valid and want to avoid hitting
|
||||||
// errors due to bad origin encoding (0 prefixes cut off).
|
// errors due to bad origin encoding (0 prefixes cut off).
|
||||||
func ToECDSAUnsafe(d []byte) *ecdsa.PrivateKey {
|
func ToECDSAUnsafe(d []byte) *ecdsa.PrivateKey {
|
||||||
|
|
@ -98,6 +98,9 @@ func toECDSA(d []byte, strict bool) (*ecdsa.PrivateKey, error) {
|
||||||
}
|
}
|
||||||
priv.D = new(big.Int).SetBytes(d)
|
priv.D = new(big.Int).SetBytes(d)
|
||||||
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(d)
|
priv.PublicKey.X, priv.PublicKey.Y = priv.PublicKey.Curve.ScalarBaseMult(d)
|
||||||
|
if priv.PublicKey.X == nil {
|
||||||
|
return nil, errors.New("invalid private key")
|
||||||
|
}
|
||||||
return priv, nil
|
return priv, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,10 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
)
|
)
|
||||||
|
|
@ -42,15 +40,20 @@ func TestKeccak256Hash(t *testing.T) {
|
||||||
checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp)
|
checkhash(t, "Sha3-256-array", func(in []byte) []byte { h := Keccak256Hash(in); return h[:] }, msg, exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToECDSAErrors(t *testing.T) {
|
||||||
|
if _, err := HexToECDSA("0000000000000000000000000000000000000000000000000000000000000000"); err == nil {
|
||||||
|
t.Fatal("HexToECDSA should've returned error")
|
||||||
|
}
|
||||||
|
if _, err := HexToECDSA("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); err == nil {
|
||||||
|
t.Fatal("HexToECDSA should've returned error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSha3(b *testing.B) {
|
func BenchmarkSha3(b *testing.B) {
|
||||||
a := []byte("hello world")
|
a := []byte("hello world")
|
||||||
amount := 1000000
|
for i := 0; i < b.N; i++ {
|
||||||
start := time.Now()
|
|
||||||
for i := 0; i < amount; i++ {
|
|
||||||
Keccak256(a)
|
Keccak256(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(amount, ":", time.Since(start))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSign(t *testing.T) {
|
func TestSign(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ package secp256k1
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
|
|
@ -42,7 +41,7 @@ import (
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#include "libsecp256k1/include/secp256k1.h"
|
#include "libsecp256k1/include/secp256k1.h"
|
||||||
extern int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar);
|
extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar);
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
|
|
@ -236,7 +235,7 @@ func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int,
|
||||||
math.ReadBits(By, point[32:])
|
math.ReadBits(By, point[32:])
|
||||||
pointPtr := (*C.uchar)(unsafe.Pointer(&point[0]))
|
pointPtr := (*C.uchar)(unsafe.Pointer(&point[0]))
|
||||||
scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0]))
|
scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0]))
|
||||||
res := C.secp256k1_pubkey_scalar_mul(context, pointPtr, scalarPtr)
|
res := C.secp256k1_ext_scalar_mul(context, pointPtr, scalarPtr)
|
||||||
|
|
||||||
// Unpack the result and clear temporaries.
|
// Unpack the result and clear temporaries.
|
||||||
x := new(big.Int).SetBytes(point[:32])
|
x := new(big.Int).SetBytes(point[:32])
|
||||||
|
|
@ -263,14 +262,10 @@ func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
||||||
// X9.62.
|
// X9.62.
|
||||||
func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
|
func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
|
||||||
byteLen := (BitCurve.BitSize + 7) >> 3
|
byteLen := (BitCurve.BitSize + 7) >> 3
|
||||||
|
|
||||||
ret := make([]byte, 1+2*byteLen)
|
ret := make([]byte, 1+2*byteLen)
|
||||||
ret[0] = 4 // uncompressed point
|
ret[0] = 4 // uncompressed point flag
|
||||||
|
math.ReadBits(x, ret[1:1+byteLen])
|
||||||
xBytes := x.Bytes()
|
math.ReadBits(y, ret[1+byteLen:])
|
||||||
copy(ret[1+byteLen-len(xBytes):], xBytes)
|
|
||||||
yBytes := y.Bytes()
|
|
||||||
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,24 +284,21 @@ func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var theCurve = new(BitCurve)
|
||||||
initonce sync.Once
|
|
||||||
theCurve *BitCurve
|
|
||||||
)
|
|
||||||
|
|
||||||
// S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1)
|
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.BitSize = 256
|
||||||
|
}
|
||||||
|
|
||||||
|
// S256 returns a BitCurve which implements secp256k1.
|
||||||
func S256() *BitCurve {
|
func S256() *BitCurve {
|
||||||
initonce.Do(func() {
|
|
||||||
// See SEC 2 section 2.7.1
|
|
||||||
// curve parameters taken from:
|
|
||||||
// http://www.secg.org/collateral/sec2_final.pdf
|
|
||||||
theCurve = new(BitCurve)
|
|
||||||
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.BitSize = 256
|
|
||||||
})
|
|
||||||
return theCurve
|
return theCurve
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ static secp256k1_context* secp256k1_context_create_sign_verify() {
|
||||||
return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// secp256k1_ecdsa_recover_pubkey recovers the public key of an encoded compact signature.
|
// secp256k1_ext_ecdsa_recover recovers the public key of an encoded compact signature.
|
||||||
//
|
//
|
||||||
// Returns: 1: recovery was successful
|
// Returns: 1: recovery was successful
|
||||||
// 0: recovery was not successful
|
// 0: recovery was not successful
|
||||||
|
|
@ -27,7 +27,7 @@ static secp256k1_context* secp256k1_context_create_sign_verify() {
|
||||||
// Out: pubkey_out: the serialized 65-byte public key of the signer (cannot be NULL)
|
// Out: pubkey_out: the serialized 65-byte public key of the signer (cannot be NULL)
|
||||||
// In: sigdata: pointer to a 65-byte signature with the recovery id at the end (cannot be NULL)
|
// In: sigdata: pointer to a 65-byte signature with the recovery id at the end (cannot be NULL)
|
||||||
// msgdata: pointer to a 32-byte message (cannot be NULL)
|
// msgdata: pointer to a 32-byte message (cannot be NULL)
|
||||||
static int secp256k1_ecdsa_recover_pubkey(
|
static int secp256k1_ext_ecdsa_recover(
|
||||||
const secp256k1_context* ctx,
|
const secp256k1_context* ctx,
|
||||||
unsigned char *pubkey_out,
|
unsigned char *pubkey_out,
|
||||||
const unsigned char *sigdata,
|
const unsigned char *sigdata,
|
||||||
|
|
@ -46,7 +46,62 @@ static int secp256k1_ecdsa_recover_pubkey(
|
||||||
return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED);
|
return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// secp256k1_pubkey_scalar_mul multiplies a point by a scalar in constant time.
|
// secp256k1_ext_ecdsa_verify verifies an encoded compact signature.
|
||||||
|
//
|
||||||
|
// Returns: 1: signature is valid
|
||||||
|
// 0: signature is invalid
|
||||||
|
// Args: ctx: pointer to a context object (cannot be NULL)
|
||||||
|
// In: sigdata: pointer to a 64-byte signature (cannot be NULL)
|
||||||
|
// msgdata: pointer to a 32-byte message (cannot be NULL)
|
||||||
|
// pubkeydata: pointer to public key data (cannot be NULL)
|
||||||
|
// pubkeylen: length of pubkeydata
|
||||||
|
static int secp256k1_ext_ecdsa_verify(
|
||||||
|
const secp256k1_context* ctx,
|
||||||
|
const unsigned char *sigdata,
|
||||||
|
const unsigned char *msgdata,
|
||||||
|
const unsigned char *pubkeydata,
|
||||||
|
size_t pubkeylen
|
||||||
|
) {
|
||||||
|
secp256k1_ecdsa_signature sig;
|
||||||
|
secp256k1_pubkey pubkey;
|
||||||
|
|
||||||
|
if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, sigdata)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, pubkeylen)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return secp256k1_ecdsa_verify(ctx, &sig, msgdata, &pubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// secp256k1_ext_reencode_pubkey decodes then encodes a public key. It can be used to
|
||||||
|
// convert between public key formats. The input/output formats are chosen depending on the
|
||||||
|
// length of the input/output buffers.
|
||||||
|
//
|
||||||
|
// Returns: 1: conversion successful
|
||||||
|
// 0: conversion unsuccessful
|
||||||
|
// Args: ctx: pointer to a context object (cannot be NULL)
|
||||||
|
// Out: out: output buffer that will contain the reencoded key (cannot be NULL)
|
||||||
|
// In: outlen: length of out (33 for compressed keys, 65 for uncompressed keys)
|
||||||
|
// pubkeydata: the input public key (cannot be NULL)
|
||||||
|
// pubkeylen: length of pubkeydata
|
||||||
|
static int secp256k1_ext_reencode_pubkey(
|
||||||
|
const secp256k1_context* ctx,
|
||||||
|
unsigned char *out,
|
||||||
|
size_t outlen,
|
||||||
|
const unsigned char *pubkeydata,
|
||||||
|
size_t pubkeylen
|
||||||
|
) {
|
||||||
|
secp256k1_pubkey pubkey;
|
||||||
|
|
||||||
|
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, pubkeylen)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
unsigned int flag = (outlen == 33) ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED;
|
||||||
|
return secp256k1_ec_pubkey_serialize(ctx, out, &outlen, &pubkey, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// secp256k1_ext_scalar_mul multiplies a point by a scalar in constant time.
|
||||||
//
|
//
|
||||||
// Returns: 1: multiplication was successful
|
// Returns: 1: multiplication was successful
|
||||||
// 0: scalar was invalid (zero or overflow)
|
// 0: scalar was invalid (zero or overflow)
|
||||||
|
|
@ -55,7 +110,7 @@ static int secp256k1_ecdsa_recover_pubkey(
|
||||||
// In: point: pointer to a 64-byte public point,
|
// In: point: pointer to a 64-byte public point,
|
||||||
// encoded as two 256bit big-endian numbers.
|
// encoded as two 256bit big-endian numbers.
|
||||||
// scalar: a 32-byte scalar with which to multiply the point
|
// scalar: a 32-byte scalar with which to multiply the point
|
||||||
int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) {
|
int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
int overflow = 0;
|
int overflow = 0;
|
||||||
secp256k1_fe feX, feY;
|
secp256k1_fe feX, feY;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,6 +56,7 @@ var (
|
||||||
ErrInvalidSignatureLen = errors.New("invalid signature length")
|
ErrInvalidSignatureLen = errors.New("invalid signature length")
|
||||||
ErrInvalidRecoveryID = errors.New("invalid signature recovery id")
|
ErrInvalidRecoveryID = errors.New("invalid signature recovery id")
|
||||||
ErrInvalidKey = errors.New("invalid private key")
|
ErrInvalidKey = errors.New("invalid private key")
|
||||||
|
ErrInvalidPubkey = errors.New("invalid public key")
|
||||||
ErrSignFailed = errors.New("signing failed")
|
ErrSignFailed = errors.New("signing failed")
|
||||||
ErrRecoverFailed = errors.New("recovery failed")
|
ErrRecoverFailed = errors.New("recovery failed")
|
||||||
)
|
)
|
||||||
|
|
@ -113,12 +115,59 @@ func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
|
||||||
sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
|
sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
|
||||||
msgdata = (*C.uchar)(unsafe.Pointer(&msg[0]))
|
msgdata = (*C.uchar)(unsafe.Pointer(&msg[0]))
|
||||||
)
|
)
|
||||||
if C.secp256k1_ecdsa_recover_pubkey(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 {
|
if C.secp256k1_ext_ecdsa_recover(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 {
|
||||||
return nil, ErrRecoverFailed
|
return nil, ErrRecoverFailed
|
||||||
}
|
}
|
||||||
return pubkey, nil
|
return pubkey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifySignature checks that the given pubkey created signature over message.
|
||||||
|
// The signature should be in [R || S] format.
|
||||||
|
func VerifySignature(pubkey, msg, signature []byte) bool {
|
||||||
|
if len(msg) != 32 || len(signature) != 64 || len(pubkey) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sigdata := (*C.uchar)(unsafe.Pointer(&signature[0]))
|
||||||
|
msgdata := (*C.uchar)(unsafe.Pointer(&msg[0]))
|
||||||
|
keydata := (*C.uchar)(unsafe.Pointer(&pubkey[0]))
|
||||||
|
return C.secp256k1_ext_ecdsa_verify(context, sigdata, msgdata, keydata, C.size_t(len(pubkey))) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecompressPubkey parses a public key in the 33-byte compressed format.
|
||||||
|
// It returns non-nil coordinates if the public key is valid.
|
||||||
|
func DecompressPubkey(pubkey []byte) (x, y *big.Int) {
|
||||||
|
if len(pubkey) != 33 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
pubkeydata = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
|
||||||
|
pubkeylen = C.size_t(len(pubkey))
|
||||||
|
out = make([]byte, 65)
|
||||||
|
outdata = (*C.uchar)(unsafe.Pointer(&out[0]))
|
||||||
|
outlen = C.size_t(len(out))
|
||||||
|
)
|
||||||
|
if C.secp256k1_ext_reencode_pubkey(context, outdata, outlen, pubkeydata, pubkeylen) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return new(big.Int).SetBytes(out[1:33]), new(big.Int).SetBytes(out[33:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressPubkey encodes a public key to 33-byte compressed format.
|
||||||
|
func CompressPubkey(x, y *big.Int) []byte {
|
||||||
|
var (
|
||||||
|
pubkey = S256().Marshal(x, y)
|
||||||
|
pubkeydata = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
|
||||||
|
pubkeylen = C.size_t(len(pubkey))
|
||||||
|
out = make([]byte, 33)
|
||||||
|
outdata = (*C.uchar)(unsafe.Pointer(&out[0]))
|
||||||
|
outlen = C.size_t(len(out))
|
||||||
|
)
|
||||||
|
if C.secp256k1_ext_reencode_pubkey(context, outdata, outlen, pubkeydata, pubkeylen) == 0 {
|
||||||
|
panic("libsecp256k1 error")
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func checkSignature(sig []byte) error {
|
func checkSignature(sig []byte) error {
|
||||||
if len(sig) != 65 {
|
if len(sig) != 65 {
|
||||||
return ErrInvalidSignatureLen
|
return ErrInvalidSignatureLen
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ecrecover returns the uncompressed public key that created the given signature.
|
||||||
func Ecrecover(hash, sig []byte) ([]byte, error) {
|
func Ecrecover(hash, sig []byte) ([]byte, error) {
|
||||||
return secp256k1.RecoverPubkey(hash, sig)
|
return secp256k1.RecoverPubkey(hash, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SigToPub returns the public key that created the given signature.
|
||||||
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
|
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
|
||||||
s, err := Ecrecover(hash, sig)
|
s, err := Ecrecover(hash, sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -58,6 +60,27 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
|
||||||
return secp256k1.Sign(hash, seckey)
|
return secp256k1.Sign(hash, seckey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifySignature checks that the given public key created signature over hash.
|
||||||
|
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
|
||||||
|
// The signature should have the 64 byte [R || S] format.
|
||||||
|
func VerifySignature(pubkey, hash, signature []byte) bool {
|
||||||
|
return secp256k1.VerifySignature(pubkey, hash, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecompressPubkey parses a public key in the 33-byte compressed format.
|
||||||
|
func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
|
||||||
|
x, y := secp256k1.DecompressPubkey(pubkey)
|
||||||
|
if x == nil {
|
||||||
|
return nil, fmt.Errorf("invalid public key")
|
||||||
|
}
|
||||||
|
return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressPubkey encodes a public key to the 33-byte compressed format.
|
||||||
|
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
|
||||||
|
return secp256k1.CompressPubkey(pubkey.X, pubkey.Y)
|
||||||
|
}
|
||||||
|
|
||||||
// S256 returns an instance of the secp256k1 curve.
|
// S256 returns an instance of the secp256k1 curve.
|
||||||
func S256() elliptic.Curve {
|
func S256() elliptic.Curve {
|
||||||
return secp256k1.S256()
|
return secp256k1.S256()
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,14 @@ package crypto
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Ecrecover returns the uncompressed public key that created the given signature.
|
||||||
func Ecrecover(hash, sig []byte) ([]byte, error) {
|
func Ecrecover(hash, sig []byte) ([]byte, error) {
|
||||||
pub, err := SigToPub(hash, sig)
|
pub, err := SigToPub(hash, sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -35,6 +38,7 @@ func Ecrecover(hash, sig []byte) ([]byte, error) {
|
||||||
return bytes, err
|
return bytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SigToPub returns the public key that created the given signature.
|
||||||
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
|
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
|
||||||
// Convert to btcec input format with 'recovery id' v at the beginning.
|
// Convert to btcec input format with 'recovery id' v at the beginning.
|
||||||
btcsig := make([]byte, 65)
|
btcsig := make([]byte, 65)
|
||||||
|
|
@ -71,6 +75,42 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
|
||||||
return sig, nil
|
return sig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifySignature checks that the given public key created signature over hash.
|
||||||
|
// The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format.
|
||||||
|
// The signature should have the 64 byte [R || S] format.
|
||||||
|
func VerifySignature(pubkey, hash, signature []byte) bool {
|
||||||
|
if len(signature) != 64 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sig := &btcec.Signature{R: new(big.Int).SetBytes(signature[:32]), S: new(big.Int).SetBytes(signature[32:])}
|
||||||
|
key, err := btcec.ParsePubKey(pubkey, btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Reject malleable signatures. libsecp256k1 does this check but btcec doesn't.
|
||||||
|
if sig.S.Cmp(secp256k1_halfN) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return sig.Verify(hash, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecompressPubkey parses a public key in the 33-byte compressed format.
|
||||||
|
func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
|
||||||
|
if len(pubkey) != 33 {
|
||||||
|
return nil, errors.New("invalid compressed public key length")
|
||||||
|
}
|
||||||
|
key, err := btcec.ParsePubKey(pubkey, btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key.ToECDSA(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressPubkey encodes a public key to the 33-byte compressed format.
|
||||||
|
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
|
||||||
|
return (*btcec.PublicKey)(pubkey).SerializeCompressed()
|
||||||
|
}
|
||||||
|
|
||||||
// S256 returns an instance of the secp256k1 curve.
|
// S256 returns an instance of the secp256k1 curve.
|
||||||
func S256() elliptic.Curve {
|
func S256() elliptic.Curve {
|
||||||
return btcec.S256()
|
return btcec.S256()
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,143 @@ package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"crypto/ecdsa"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRecoverSanity(t *testing.T) {
|
var (
|
||||||
msg, _ := hex.DecodeString("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008")
|
testmsg = hexutil.MustDecode("0xce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008")
|
||||||
sig, _ := hex.DecodeString("90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301")
|
testsig = hexutil.MustDecode("0x90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301")
|
||||||
pubkey1, _ := hex.DecodeString("04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652")
|
testpubkey = hexutil.MustDecode("0x04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652")
|
||||||
pubkey2, err := Ecrecover(msg, sig)
|
testpubkeyc = hexutil.MustDecode("0x02e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEcrecover(t *testing.T) {
|
||||||
|
pubkey, err := Ecrecover(testmsg, testsig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("recover error: %s", err)
|
t.Fatalf("recover error: %s", err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(pubkey1, pubkey2) {
|
if !bytes.Equal(pubkey, testpubkey) {
|
||||||
t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2)
|
t.Errorf("pubkey mismatch: want: %x have: %x", testpubkey, pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySignature(t *testing.T) {
|
||||||
|
sig := testsig[:len(testsig)-1] // remove recovery id
|
||||||
|
if !VerifySignature(testpubkey, testmsg, sig) {
|
||||||
|
t.Errorf("can't verify signature with uncompressed key")
|
||||||
|
}
|
||||||
|
if !VerifySignature(testpubkeyc, testmsg, sig) {
|
||||||
|
t.Errorf("can't verify signature with compressed key")
|
||||||
|
}
|
||||||
|
|
||||||
|
if VerifySignature(nil, testmsg, sig) {
|
||||||
|
t.Errorf("signature valid with no key")
|
||||||
|
}
|
||||||
|
if VerifySignature(testpubkey, nil, sig) {
|
||||||
|
t.Errorf("signature valid with no message")
|
||||||
|
}
|
||||||
|
if VerifySignature(testpubkey, testmsg, nil) {
|
||||||
|
t.Errorf("nil signature valid")
|
||||||
|
}
|
||||||
|
if VerifySignature(testpubkey, testmsg, append(common.CopyBytes(sig), 1, 2, 3)) {
|
||||||
|
t.Errorf("signature valid with extra bytes at the end")
|
||||||
|
}
|
||||||
|
if VerifySignature(testpubkey, testmsg, sig[:len(sig)-2]) {
|
||||||
|
t.Errorf("signature valid even though it's incomplete")
|
||||||
|
}
|
||||||
|
wrongkey := common.CopyBytes(testpubkey)
|
||||||
|
wrongkey[10]++
|
||||||
|
if VerifySignature(wrongkey, testmsg, sig) {
|
||||||
|
t.Errorf("signature valid with with wrong public key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test checks that VerifySignature rejects malleable signatures with s > N/2.
|
||||||
|
func TestVerifySignatureMalleable(t *testing.T) {
|
||||||
|
sig := hexutil.MustDecode("0x638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454")
|
||||||
|
key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
|
||||||
|
msg := hexutil.MustDecode("0xd301ce462d3e639518f482c7f03821fec1e602018630ce621e1e7851c12343a6")
|
||||||
|
if VerifySignature(key, msg, sig) {
|
||||||
|
t.Error("VerifySignature returned true for malleable signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecompressPubkey(t *testing.T) {
|
||||||
|
key, err := DecompressPubkey(testpubkeyc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if uncompressed := FromECDSAPub(key); !bytes.Equal(uncompressed, testpubkey) {
|
||||||
|
t.Errorf("wrong public key result: got %x, want %x", uncompressed, testpubkey)
|
||||||
|
}
|
||||||
|
if _, err := DecompressPubkey(nil); err == nil {
|
||||||
|
t.Errorf("no error for nil pubkey")
|
||||||
|
}
|
||||||
|
if _, err := DecompressPubkey(testpubkeyc[:5]); err == nil {
|
||||||
|
t.Errorf("no error for incomplete pubkey")
|
||||||
|
}
|
||||||
|
if _, err := DecompressPubkey(append(common.CopyBytes(testpubkeyc), 1, 2, 3)); err == nil {
|
||||||
|
t.Errorf("no error for pubkey with extra bytes at the end")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompressPubkey(t *testing.T) {
|
||||||
|
key := &ecdsa.PublicKey{
|
||||||
|
Curve: S256(),
|
||||||
|
X: math.MustParseBig256("0xe32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a"),
|
||||||
|
Y: math.MustParseBig256("0x0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652"),
|
||||||
|
}
|
||||||
|
compressed := CompressPubkey(key)
|
||||||
|
if !bytes.Equal(compressed, testpubkeyc) {
|
||||||
|
t.Errorf("wrong public key result: got %x, want %x", compressed, testpubkeyc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPubkeyRandom(t *testing.T) {
|
||||||
|
const runs = 200
|
||||||
|
|
||||||
|
for i := 0; i < runs; i++ {
|
||||||
|
key, err := GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("iteration %d: %v", i, err)
|
||||||
|
}
|
||||||
|
pubkey2, err := DecompressPubkey(CompressPubkey(&key.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("iteration %d: %v", i, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(key.PublicKey, *pubkey2) {
|
||||||
|
t.Fatalf("iteration %d: keys not equal", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEcrecoverSignature(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err := Ecrecover(testmsg, testsig); err != nil {
|
||||||
|
b.Fatal("ecrecover error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkVerifySignature(b *testing.B) {
|
||||||
|
sig := testsig[:len(testsig)-1] // remove recovery id
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if !VerifySignature(testpubkey, testmsg, sig) {
|
||||||
|
b.Fatal("verify error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecompressPubkey(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if _, err := DecompressPubkey(testpubkeyc); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ The client's UI uses [React][React] with JSX syntax, which is validated by the [
|
||||||
|
|
||||||
### Development and bundling
|
### Development and bundling
|
||||||
|
|
||||||
As the dashboard depends on certain NPM packages (which are not included in the go-ethereum repo), these need to be installed first:
|
As the dashboard depends on certain NPM packages (which are not included in the `go-ethereum` repo), these need to be installed first:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ (cd dashboard/assets && npm install)
|
$ (cd dashboard/assets && npm install)
|
||||||
|
$ (cd dashboard/assets && ./node_modules/.bin/flow-typed install)
|
||||||
```
|
```
|
||||||
|
|
||||||
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources:
|
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources:
|
||||||
|
|
@ -22,13 +23,20 @@ $ (cd dashboard/assets && ./node_modules/.bin/webpack --watch)
|
||||||
$ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5
|
$ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5
|
||||||
```
|
```
|
||||||
|
|
||||||
To bundle up the final UI into Geth, run `webpack` and `go generate`:
|
To bundle up the final UI into Geth, run `go generate`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ (cd dashboard/assets && ./node_modules/.bin/webpack)
|
|
||||||
$ go generate ./dashboard
|
$ go generate ./dashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Static type checking
|
||||||
|
|
||||||
|
Since JavaScript doesn't provide type safety, [Flow][Flow] is used to check types. These are only useful during development, so at the end of the process Babel will strip them.
|
||||||
|
|
||||||
|
To take advantage of static type checking, your IDE needs to be prepared for it. In case of [Atom][Atom] a configuration guide can be found [here][Atom config]: Install the [Nuclide][Nuclide] package for Flow support, making sure it installs all of its support packages by enabling `Install Recommended Packages on Startup`, and set the path of the `flow-bin` which were installed previously by `npm`.
|
||||||
|
|
||||||
|
For more IDE support install the `linter-eslint` package too, which finds the `.eslintrc` file, and provides real-time linting. Atom warns, that these two packages are incompatible, but they seem to work well together. For third-party library errors and auto-completion [flow-typed][flow-typed] is used.
|
||||||
|
|
||||||
### Have fun
|
### Have fun
|
||||||
|
|
||||||
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
|
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
|
||||||
|
|
@ -44,3 +52,8 @@ $ go generate ./dashboard
|
||||||
[WA]: http://webpack.github.io/analyse/
|
[WA]: http://webpack.github.io/analyse/
|
||||||
[WV]: http://chrisbateman.github.io/webpack-visualizer/
|
[WV]: http://chrisbateman.github.io/webpack-visualizer/
|
||||||
[Node.js]: https://nodejs.org/en/
|
[Node.js]: https://nodejs.org/en/
|
||||||
|
[Flow]: https://flow.org/
|
||||||
|
[Atom]: https://atom.io/
|
||||||
|
[Atom config]: https://medium.com/@fastphrase/integrating-flow-into-a-react-project-fbbc2f130eed
|
||||||
|
[Nuclide]: https://nuclide.io/docs/quick-start/getting-started/
|
||||||
|
[flow-typed]: https://github.com/flowtype/flow-typed
|
||||||
|
|
|
||||||
42113
dashboard/assets.go
42113
dashboard/assets.go
File diff suppressed because one or more lines are too long
|
|
@ -16,37 +16,68 @@
|
||||||
|
|
||||||
// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
|
// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
|
||||||
{
|
{
|
||||||
"plugins": [
|
'env': {
|
||||||
"react"
|
'browser': true,
|
||||||
],
|
'node': true,
|
||||||
"parser": "babel-eslint",
|
'es6': true,
|
||||||
"parserOptions": {
|
},
|
||||||
"ecmaFeatures": {
|
'parser': 'babel-eslint',
|
||||||
"jsx": true,
|
'parserOptions': {
|
||||||
"modules": true
|
'sourceType': 'module',
|
||||||
}
|
'ecmaVersion': 6,
|
||||||
},
|
'ecmaFeatures': {
|
||||||
"rules": {
|
'jsx': true,
|
||||||
"react/prefer-es6-class": 2,
|
}
|
||||||
"react/prefer-stateless-function": 2,
|
},
|
||||||
"react/jsx-pascal-case": 2,
|
'extends': 'airbnb',
|
||||||
"react/jsx-closing-bracket-location": [1, {"selfClosing": "tag-aligned", "nonEmpty": "tag-aligned"}],
|
'plugins': [
|
||||||
"react/jsx-closing-tag-location": 1,
|
'flowtype',
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
'react',
|
||||||
"no-multi-spaces": "error",
|
],
|
||||||
"react/jsx-tag-spacing": 2,
|
'rules': {
|
||||||
"react/jsx-curly-spacing": [2, {"when": "never", "children": true}],
|
'no-tabs': 'off',
|
||||||
"react/jsx-boolean-value": 2,
|
'indent': ['error', 'tab'],
|
||||||
"react/no-string-refs": 2,
|
'react/jsx-indent': ['error', 'tab'],
|
||||||
"react/jsx-wrap-multilines": 2,
|
'react/jsx-indent-props': ['error', 'tab'],
|
||||||
"react/self-closing-comp": 2,
|
'react/prefer-stateless-function': 'off',
|
||||||
"react/jsx-no-bind": 2,
|
|
||||||
"react/require-render-return": 2,
|
// Specifies the maximum length of a line.
|
||||||
"react/no-is-mounted": 2,
|
'max-len': ['warn', 120, 2, {
|
||||||
"key-spacing": ["error", {"align": {
|
'ignoreUrls': true,
|
||||||
"beforeColon": false,
|
'ignoreComments': false,
|
||||||
"afterColon": true,
|
'ignoreRegExpLiterals': true,
|
||||||
"on": "value"
|
'ignoreStrings': true,
|
||||||
}}]
|
'ignoreTemplateLiterals': true,
|
||||||
}
|
}],
|
||||||
|
// Enforces spacing between keys and values in object literal properties.
|
||||||
|
'key-spacing': ['error', {'align': {
|
||||||
|
'beforeColon': false,
|
||||||
|
'afterColon': true,
|
||||||
|
'on': 'value'
|
||||||
|
}}],
|
||||||
|
// Prohibits padding inside curly braces.
|
||||||
|
'object-curly-spacing': ['error', 'never'],
|
||||||
|
'no-use-before-define': 'off', // messageAPI
|
||||||
|
'default-case': 'off',
|
||||||
|
|
||||||
|
'flowtype/boolean-style': ['error', 'boolean'],
|
||||||
|
'flowtype/define-flow-type': 'warn',
|
||||||
|
'flowtype/generic-spacing': ['error', 'never'],
|
||||||
|
'flowtype/no-primitive-constructor-types': 'error',
|
||||||
|
'flowtype/no-weak-types': 'error',
|
||||||
|
'flowtype/object-type-delimiter': ['error', 'comma'],
|
||||||
|
'flowtype/require-valid-file-annotation': 'error',
|
||||||
|
'flowtype/semi': ['error', 'always'],
|
||||||
|
'flowtype/space-after-type-colon': ['error', 'always'],
|
||||||
|
'flowtype/space-before-generic-bracket': ['error', 'never'],
|
||||||
|
'flowtype/space-before-type-colon': ['error', 'never'],
|
||||||
|
'flowtype/union-intersection-spacing': ['error', 'always'],
|
||||||
|
'flowtype/use-flow-type': 'warn',
|
||||||
|
'flowtype/valid-syntax': 'warn',
|
||||||
|
},
|
||||||
|
'settings': {
|
||||||
|
'flowtype': {
|
||||||
|
'onlyFilesWithFlowAnnotation': true,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
dashboard/assets/.flowconfig
Normal file
9
dashboard/assets/.flowconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[ignore]
|
||||||
|
<PROJECT_ROOT>/node_modules/material-ui/.*\.js\.flow
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
<PROJECT_ROOT>/flow-typed/
|
||||||
|
node_modules/jss/flow-typed
|
||||||
|
|
||||||
|
[options]
|
||||||
|
include_warnings=true
|
||||||
64
dashboard/assets/components/Body.jsx
Normal file
64
dashboard/assets/components/Body.jsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
|
||||||
|
import SideBar from './SideBar';
|
||||||
|
import Main from './Main';
|
||||||
|
import type {Content} from '../types/content';
|
||||||
|
|
||||||
|
// Styles for the Body component.
|
||||||
|
const styles = () => ({
|
||||||
|
body: {
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
opened: boolean,
|
||||||
|
changeContent: () => {},
|
||||||
|
active: string,
|
||||||
|
content: Content,
|
||||||
|
shouldUpdate: Object,
|
||||||
|
};
|
||||||
|
// Body renders the body of the dashboard.
|
||||||
|
class Body extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.body}>
|
||||||
|
<SideBar
|
||||||
|
opened={this.props.opened}
|
||||||
|
changeContent={this.props.changeContent}
|
||||||
|
/>
|
||||||
|
<Main
|
||||||
|
active={this.props.active}
|
||||||
|
content={this.props.content}
|
||||||
|
shouldUpdate={this.props.shouldUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(Body);
|
||||||
49
dashboard/assets/components/ChartGrid.jsx
Normal file
49
dashboard/assets/components/ChartGrid.jsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import type {Node} from 'react';
|
||||||
|
|
||||||
|
import Grid from 'material-ui/Grid';
|
||||||
|
import {ResponsiveContainer} from 'recharts';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
spacing: number,
|
||||||
|
children: Node,
|
||||||
|
};
|
||||||
|
// ChartGrid renders a grid container for responsive charts.
|
||||||
|
// The children are Recharts components extended with the Material-UI's xs property.
|
||||||
|
class ChartGrid extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={this.props.spacing}>
|
||||||
|
{
|
||||||
|
React.Children.map(this.props.children, child => (
|
||||||
|
<Grid item xs={child.props.xs}>
|
||||||
|
<ResponsiveContainer width="100%" height={child.props.height}>
|
||||||
|
{React.cloneElement(child, {data: child.props.values.map(value => ({value}))})}
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Grid>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChartGrid;
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -14,39 +16,78 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// isNullOrUndefined returns true if the given variable is null or undefined.
|
type ProvidedMenuProp = {|title: string, icon: string|};
|
||||||
export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
|
const menuSkeletons: Array<{|id: string, menu: ProvidedMenuProp|}> = [
|
||||||
|
{
|
||||||
export const LIMIT = {
|
id: 'home',
|
||||||
memory: 200, // Maximum number of memory data samples.
|
menu: {
|
||||||
traffic: 200, // Maximum number of traffic data samples.
|
title: 'Home',
|
||||||
log: 200, // Maximum number of logs.
|
icon: 'home',
|
||||||
};
|
},
|
||||||
|
}, {
|
||||||
|
id: 'chain',
|
||||||
|
menu: {
|
||||||
|
title: 'Chain',
|
||||||
|
icon: 'link',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'txpool',
|
||||||
|
menu: {
|
||||||
|
title: 'TxPool',
|
||||||
|
icon: 'credit-card',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'network',
|
||||||
|
menu: {
|
||||||
|
title: 'Network',
|
||||||
|
icon: 'globe',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'system',
|
||||||
|
menu: {
|
||||||
|
title: 'System',
|
||||||
|
icon: 'tachometer',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'logs',
|
||||||
|
menu: {
|
||||||
|
title: 'Logs',
|
||||||
|
icon: 'list',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export type MenuProp = {|...ProvidedMenuProp, id: string|};
|
||||||
// The sidebar menu and the main content are rendered based on these elements.
|
// The sidebar menu and the main content are rendered based on these elements.
|
||||||
export const TAGS = (() => {
|
// Using the id is circumstantial in some cases, so it is better to insert it also as a value.
|
||||||
const T = {
|
// This way the mistyping is prevented.
|
||||||
home: { title: "Home", },
|
export const MENU: Map<string, {...MenuProp}> = new Map(menuSkeletons.map(({id, menu}) => ([id, {id, ...menu}])));
|
||||||
chain: { title: "Chain", },
|
|
||||||
transactions: { title: "Transactions", },
|
|
||||||
network: { title: "Network", },
|
|
||||||
system: { title: "System", },
|
|
||||||
logs: { title: "Logs", },
|
|
||||||
};
|
|
||||||
// Using the key is circumstantial in some cases, so it is better to insert it also as a value.
|
|
||||||
// This way the mistyping is prevented.
|
|
||||||
for(let key in T) {
|
|
||||||
T[key]['id'] = key;
|
|
||||||
}
|
|
||||||
return T;
|
|
||||||
})();
|
|
||||||
|
|
||||||
export const DATA_KEYS = (() => {
|
type ProvidedSampleProp = {|limit: number|};
|
||||||
const DK = {};
|
const sampleSkeletons: Array<{|id: string, sample: ProvidedSampleProp|}> = [
|
||||||
["memory", "traffic", "logs"].map(key => {
|
{
|
||||||
DK[key] = key;
|
id: 'memory',
|
||||||
});
|
sample: {
|
||||||
return DK;
|
limit: 200,
|
||||||
})();
|
},
|
||||||
|
}, {
|
||||||
|
id: 'traffic',
|
||||||
|
sample: {
|
||||||
|
limit: 200,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'logs',
|
||||||
|
sample: {
|
||||||
|
limit: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export type SampleProp = {|...ProvidedSampleProp, id: string|};
|
||||||
|
export const SAMPLE: Map<string, {...SampleProp}> = new Map(sampleSkeletons.map(({id, sample}) => ([id, {id, ...sample}])));
|
||||||
|
|
||||||
// Temporary - taken from Material-UI
|
export const DURATION = 200;
|
||||||
export const DRAWER_WIDTH = 240;
|
|
||||||
|
export const LENS: Map<string, string> = new Map([
|
||||||
|
'content',
|
||||||
|
...menuSkeletons.map(({id}) => id),
|
||||||
|
...sampleSkeletons.map(({id}) => id),
|
||||||
|
].map(lens => [lens, lens]));
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,155 +17,183 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import SideBar from './SideBar.jsx';
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
import Header from './Header.jsx';
|
import {lensPath, view, set} from 'ramda';
|
||||||
import Main from "./Main.jsx";
|
|
||||||
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
|
|
||||||
|
|
||||||
// Styles for the Dashboard component.
|
import Header from './Header';
|
||||||
|
import Body from './Body';
|
||||||
|
import {MENU, SAMPLE} from './Common';
|
||||||
|
import type {Message, HomeMessage, LogsMessage, Chart} from '../types/message';
|
||||||
|
import type {Content} from '../types/content';
|
||||||
|
|
||||||
|
// appender appends an array (A) to the end of another array (B) in the state.
|
||||||
|
// lens is the path of B in the state, samples is A, and limit is the maximum size of the changed array.
|
||||||
|
//
|
||||||
|
// appender retrieves a function, which overrides the state's value at lens, and returns with the overridden state.
|
||||||
|
const appender = (lens, samples, limit) => (state) => {
|
||||||
|
const newSamples = [
|
||||||
|
...view(lens, state), // retrieves a specific value of the state at the given path (lens).
|
||||||
|
...samples,
|
||||||
|
];
|
||||||
|
// set is a function of ramda.js, which needs the path, the new value, the original state, and retrieves
|
||||||
|
// the altered state.
|
||||||
|
return set(
|
||||||
|
lens,
|
||||||
|
newSamples.slice(newSamples.length > limit ? newSamples.length - limit : 0),
|
||||||
|
state
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Lenses for specific data fields in the state, used for a clearer deep update.
|
||||||
|
// NOTE: This solution will be changed very likely.
|
||||||
|
const memoryLens = lensPath(['content', 'home', 'memory']);
|
||||||
|
const trafficLens = lensPath(['content', 'home', 'traffic']);
|
||||||
|
const logLens = lensPath(['content', 'logs', 'log']);
|
||||||
|
// styles retrieves the styles for the Dashboard component.
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
appFrame: {
|
dashboard: {
|
||||||
position: 'relative',
|
display: 'flex',
|
||||||
display: 'flex',
|
flexFlow: 'column',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: theme.palette.background.default,
|
background: theme.palette.background.default,
|
||||||
},
|
zIndex: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
};
|
||||||
|
type State = {
|
||||||
|
active: string, // active menu
|
||||||
|
sideBar: boolean, // true if the sidebar is opened
|
||||||
|
content: $Shape<Content>, // the visualized data
|
||||||
|
shouldUpdate: Set<string> // labels for the components, which need to rerender based on the incoming message
|
||||||
|
};
|
||||||
|
// Dashboard is the main component, which renders the whole page, makes connection with the server and
|
||||||
|
// listens for messages. When there is an incoming message, updates the page's content correspondingly.
|
||||||
|
class Dashboard extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
active: MENU.get('home').id,
|
||||||
|
sideBar: true,
|
||||||
|
content: {home: {memory: [], traffic: []}, logs: {log: []}},
|
||||||
|
shouldUpdate: new Set(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard is the main component, which renders the whole page, makes connection with the server and listens for messages.
|
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
|
||||||
// When there is an incoming message, updates the page's content correspondingly.
|
componentDidMount() {
|
||||||
class Dashboard extends Component {
|
this.reconnect();
|
||||||
constructor(props) {
|
}
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
active: TAGS.home.id, // active menu
|
|
||||||
sideBar: true, // true if the sidebar is opened
|
|
||||||
memory: [],
|
|
||||||
traffic: [],
|
|
||||||
logs: [],
|
|
||||||
shouldUpdate: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
|
// reconnect establishes a websocket connection with the server, listens for incoming messages
|
||||||
componentDidMount() {
|
// and tries to reconnect on connection loss.
|
||||||
this.reconnect();
|
reconnect = () => {
|
||||||
}
|
this.setState({
|
||||||
|
content: {home: {memory: [], traffic: []}, logs: {log: []}},
|
||||||
|
});
|
||||||
|
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://') + window.location.host}/api`);
|
||||||
|
server.onmessage = (event) => {
|
||||||
|
const msg: Message = JSON.parse(event.data);
|
||||||
|
if (!msg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.update(msg);
|
||||||
|
};
|
||||||
|
server.onclose = () => {
|
||||||
|
setTimeout(this.reconnect, 3000);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// reconnect establishes a websocket connection with the server, listens for incoming messages
|
// samples retrieves the raw data of a chart field from the incoming message.
|
||||||
// and tries to reconnect on connection loss.
|
samples = (chart: Chart) => {
|
||||||
reconnect = () => {
|
let s = [];
|
||||||
const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
|
if (chart.history) {
|
||||||
|
s = chart.history.map(({value}) => (value || 0)); // traffic comes without value at the beginning
|
||||||
|
}
|
||||||
|
if (chart.new) {
|
||||||
|
s = [...s, chart.new.value || 0];
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
server.onmessage = event => {
|
// handleHome changes the home-menu related part of the state.
|
||||||
const msg = JSON.parse(event.data);
|
handleHome = (home: HomeMessage) => {
|
||||||
if (isNullOrUndefined(msg)) {
|
this.setState((prevState) => {
|
||||||
return;
|
let newState = prevState;
|
||||||
}
|
newState.shouldUpdate = new Set();
|
||||||
this.update(msg);
|
if (home.memory) {
|
||||||
};
|
newState = appender(memoryLens, this.samples(home.memory), SAMPLE.get('memory').limit)(newState);
|
||||||
|
newState.shouldUpdate.add('memory');
|
||||||
|
}
|
||||||
|
if (home.traffic) {
|
||||||
|
newState = appender(trafficLens, this.samples(home.traffic), SAMPLE.get('traffic').limit)(newState);
|
||||||
|
newState.shouldUpdate.add('traffic');
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
server.onclose = () => {
|
// handleLogs changes the logs-menu related part of the state.
|
||||||
setTimeout(this.reconnect, 3000);
|
handleLogs = (logs: LogsMessage) => {
|
||||||
};
|
this.setState((prevState) => {
|
||||||
};
|
let newState = prevState;
|
||||||
|
newState.shouldUpdate = new Set();
|
||||||
|
if (logs.log) {
|
||||||
|
newState = appender(logLens, [logs.log], SAMPLE.get('logs').limit)(newState);
|
||||||
|
newState.shouldUpdate.add('logs');
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// update analyzes the incoming message, and updates the charts' content correspondingly.
|
// update analyzes the incoming message, and updates the charts' content correspondingly.
|
||||||
update = msg => {
|
update = (msg: Message) => {
|
||||||
console.log(msg);
|
if (msg.home) {
|
||||||
this.setState(prevState => {
|
this.handleHome(msg.home);
|
||||||
let newState = [];
|
}
|
||||||
newState.shouldUpdate = {};
|
if (msg.logs) {
|
||||||
const insert = (key, values, limit) => {
|
this.handleLogs(msg.logs);
|
||||||
newState[key] = [...prevState[key], ...values];
|
}
|
||||||
while (newState[key].length > limit) {
|
};
|
||||||
newState[key].shift();
|
|
||||||
}
|
|
||||||
newState.shouldUpdate[key] = true;
|
|
||||||
};
|
|
||||||
// (Re)initialize the state with the past data.
|
|
||||||
if (!isNullOrUndefined(msg.history)) {
|
|
||||||
const memory = DATA_KEYS.memory;
|
|
||||||
const traffic = DATA_KEYS.traffic;
|
|
||||||
newState[memory] = [];
|
|
||||||
newState[traffic] = [];
|
|
||||||
if (!isNullOrUndefined(msg.history.memorySamples)) {
|
|
||||||
newState[memory] = msg.history.memorySamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
|
|
||||||
while (newState[memory].length > LIMIT.memory) {
|
|
||||||
newState[memory].shift();
|
|
||||||
}
|
|
||||||
newState.shouldUpdate[memory] = true;
|
|
||||||
}
|
|
||||||
if (!isNullOrUndefined(msg.history.trafficSamples)) {
|
|
||||||
newState[traffic] = msg.history.trafficSamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
|
|
||||||
while (newState[traffic].length > LIMIT.traffic) {
|
|
||||||
newState[traffic].shift();
|
|
||||||
}
|
|
||||||
newState.shouldUpdate[traffic] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Insert the new data samples.
|
|
||||||
if (!isNullOrUndefined(msg.memory)) {
|
|
||||||
insert(DATA_KEYS.memory, [isNullOrUndefined(msg.memory.value) ? 0 : msg.memory.value], LIMIT.memory);
|
|
||||||
}
|
|
||||||
if (!isNullOrUndefined(msg.traffic)) {
|
|
||||||
insert(DATA_KEYS.traffic, [isNullOrUndefined(msg.traffic.value) ? 0 : msg.traffic.value], LIMIT.traffic);
|
|
||||||
}
|
|
||||||
if (!isNullOrUndefined(msg.log)) {
|
|
||||||
insert(DATA_KEYS.logs, [msg.log], LIMIT.log);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newState;
|
// changeContent sets the active label, which is used at the content rendering.
|
||||||
});
|
changeContent = (newActive: string) => {
|
||||||
};
|
this.setState(prevState => (prevState.active !== newActive ? {active: newActive} : {}));
|
||||||
|
};
|
||||||
|
|
||||||
// The change of the active label on the SideBar component will trigger a new render in the Main component.
|
// openSideBar opens the sidebar.
|
||||||
changeContent = active => {
|
openSideBar = () => {
|
||||||
this.setState(prevState => prevState.active !== active ? {active: active} : {});
|
this.setState({sideBar: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
openSideBar = () => {
|
// closeSideBar closes the sidebar.
|
||||||
this.setState({sideBar: true});
|
closeSideBar = () => {
|
||||||
};
|
this.setState({sideBar: false});
|
||||||
|
};
|
||||||
|
|
||||||
closeSideBar = () => {
|
render() {
|
||||||
this.setState({sideBar: false});
|
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
// The classes property is injected by withStyles().
|
<div className={classes.dashboard}>
|
||||||
const {classes} = this.props;
|
<Header
|
||||||
|
opened={this.state.sideBar}
|
||||||
return (
|
openSideBar={this.openSideBar}
|
||||||
<div className={classes.appFrame}>
|
closeSideBar={this.closeSideBar}
|
||||||
<Header
|
/>
|
||||||
opened={this.state.sideBar}
|
<Body
|
||||||
open={this.openSideBar}
|
opened={this.state.sideBar}
|
||||||
/>
|
changeContent={this.changeContent}
|
||||||
<SideBar
|
active={this.state.active}
|
||||||
opened={this.state.sideBar}
|
content={this.state.content}
|
||||||
close={this.closeSideBar}
|
shouldUpdate={this.state.shouldUpdate}
|
||||||
changeContent={this.changeContent}
|
/>
|
||||||
/>
|
</div>
|
||||||
<Main
|
);
|
||||||
opened={this.state.sideBar}
|
}
|
||||||
active={this.state.active}
|
|
||||||
memory={this.state.memory}
|
|
||||||
traffic={this.state.traffic}
|
|
||||||
logs={this.state.logs}
|
|
||||||
shouldUpdate={this.state.shouldUpdate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dashboard.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(Dashboard);
|
export default withStyles(styles)(Dashboard);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,73 +17,89 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
import AppBar from 'material-ui/AppBar';
|
import AppBar from 'material-ui/AppBar';
|
||||||
import Toolbar from 'material-ui/Toolbar';
|
import Toolbar from 'material-ui/Toolbar';
|
||||||
import Typography from 'material-ui/Typography';
|
import Transition from 'react-transition-group/Transition';
|
||||||
import IconButton from 'material-ui/IconButton';
|
import IconButton from 'material-ui/IconButton';
|
||||||
import MenuIcon from 'material-ui-icons/Menu';
|
import Typography from 'material-ui/Typography';
|
||||||
|
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
|
||||||
|
|
||||||
import {DRAWER_WIDTH} from './Common.jsx';
|
import {DURATION} from './Common';
|
||||||
|
|
||||||
|
// arrowDefault is the default style of the arrow button.
|
||||||
|
const arrowDefault = {
|
||||||
|
transition: `transform ${DURATION}ms`,
|
||||||
|
};
|
||||||
|
// arrowTransition is the additional style of the arrow button corresponding to the transition's state.
|
||||||
|
const arrowTransition = {
|
||||||
|
entered: {transform: 'rotate(180deg)'},
|
||||||
|
};
|
||||||
// Styles for the Header component.
|
// Styles for the Header component.
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
appBar: {
|
header: {
|
||||||
position: 'absolute',
|
backgroundColor: theme.palette.background.appBar,
|
||||||
transition: theme.transitions.create(['margin', 'width'], {
|
color: theme.palette.getContrastText(theme.palette.background.appBar),
|
||||||
easing: theme.transitions.easing.sharp,
|
zIndex: theme.zIndex.appBar,
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
},
|
||||||
}),
|
toolbar: {
|
||||||
},
|
paddingLeft: theme.spacing.unit,
|
||||||
appBarShift: {
|
paddingRight: theme.spacing.unit,
|
||||||
marginLeft: DRAWER_WIDTH,
|
},
|
||||||
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
mainText: {
|
||||||
transition: theme.transitions.create(['margin', 'width'], {
|
paddingLeft: theme.spacing.unit,
|
||||||
easing: theme.transitions.easing.easeOut,
|
},
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
menuButton: {
|
|
||||||
marginLeft: 12,
|
|
||||||
marginRight: 20,
|
|
||||||
},
|
|
||||||
hide: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
opened: boolean,
|
||||||
|
openSideBar: () => {},
|
||||||
|
closeSideBar: () => {},
|
||||||
|
};
|
||||||
|
// Header renders the header of the dashboard.
|
||||||
|
class Header extends Component<Props> {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return nextProps.opened !== this.props.opened;
|
||||||
|
}
|
||||||
|
|
||||||
// Header renders a header, which contains a sidebar opener icon when that is closed.
|
// changeSideBar opens or closes the sidebar corresponding to the previous state.
|
||||||
class Header extends Component {
|
changeSideBar = () => {
|
||||||
render() {
|
if (this.props.opened) {
|
||||||
// The classes property is injected by withStyles().
|
this.props.closeSideBar();
|
||||||
const {classes} = this.props;
|
} else {
|
||||||
|
this.props.openSideBar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
// arrowButton is connected to the sidebar; changes its state.
|
||||||
<AppBar className={classNames(classes.appBar, this.props.opened && classes.appBarShift)}>
|
arrowButton = (transitionState: string) => (
|
||||||
<Toolbar disableGutters={!this.props.opened}>
|
<IconButton onClick={this.changeSideBar}>
|
||||||
<IconButton
|
<ChevronLeftIcon
|
||||||
color="contrast"
|
style={{
|
||||||
aria-label="open drawer"
|
...arrowDefault,
|
||||||
onClick={this.props.open}
|
...arrowTransition[transitionState],
|
||||||
className={classNames(classes.menuButton, this.props.opened && classes.hide)}
|
}}
|
||||||
>
|
/>
|
||||||
<MenuIcon />
|
</IconButton>
|
||||||
</IconButton>
|
);
|
||||||
<Typography type="title" color="inherit" noWrap>
|
|
||||||
Go Ethereum Dashboard
|
render() {
|
||||||
</Typography>
|
const {classes, opened} = this.props; // The classes property is injected by withStyles().
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
return (
|
||||||
);
|
<AppBar position="static" className={classes.header}>
|
||||||
}
|
<Toolbar className={classes.toolbar}>
|
||||||
|
<Transition mountOnEnter in={opened} timeout={{enter: DURATION}}>
|
||||||
|
{this.arrowButton}
|
||||||
|
</Transition>
|
||||||
|
<Typography type="title" color="inherit" noWrap className={classes.mainText}>
|
||||||
|
Go Ethereum Dashboard
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
opened: PropTypes.bool.isRequired,
|
|
||||||
open: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(Header);
|
export default withStyles(styles)(Header);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,75 +17,56 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Grid from 'material-ui/Grid';
|
|
||||||
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line, ResponsiveContainer} from 'recharts';
|
|
||||||
import {withTheme} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import {isNullOrUndefined, DATA_KEYS} from "./Common.jsx";
|
import withTheme from 'material-ui/styles/withTheme';
|
||||||
|
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line} from 'recharts';
|
||||||
|
|
||||||
// ChartGrid renders a grid container for responsive charts.
|
import ChartGrid from './ChartGrid';
|
||||||
// The children are Recharts components extended with the Material-UI's xs property.
|
import type {ChartEntry} from '../types/message';
|
||||||
class ChartGrid extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Grid container spacing={this.props.spacing}>
|
|
||||||
{
|
|
||||||
React.Children.map(this.props.children, child => (
|
|
||||||
<Grid item xs={child.props.xs}>
|
|
||||||
<ResponsiveContainer width="100%" height={child.props.height}>
|
|
||||||
{React.cloneElement(child, {data: child.props.values.map(value => ({value: value}))})}
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</Grid>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ChartGrid.propTypes = {
|
export type Props = {
|
||||||
spacing: PropTypes.number.isRequired,
|
theme: Object,
|
||||||
|
memory: Array<ChartEntry>,
|
||||||
|
traffic: Array<ChartEntry>,
|
||||||
|
shouldUpdate: Object,
|
||||||
};
|
};
|
||||||
|
// Home renders the home content.
|
||||||
|
class Home extends Component<Props> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
const {theme} = props; // The theme property is injected by withTheme().
|
||||||
|
this.memoryColor = theme.palette.primary[300];
|
||||||
|
this.trafficColor = theme.palette.secondary[300];
|
||||||
|
}
|
||||||
|
|
||||||
// Home renders the home component.
|
shouldComponentUpdate(nextProps) {
|
||||||
class Home extends Component {
|
return nextProps.shouldUpdate.has('memory') || nextProps.shouldUpdate.has('traffic');
|
||||||
shouldComponentUpdate(nextProps) {
|
}
|
||||||
return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) ||
|
|
||||||
!isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {theme} = this.props;
|
const {memory, traffic} = this.props;
|
||||||
const memoryColor = theme.palette.primary[300];
|
|
||||||
const trafficColor = theme.palette.secondary[300];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartGrid spacing={24}>
|
<ChartGrid spacing={24}>
|
||||||
<AreaChart xs={6} height={300} values={this.props.memory}>
|
<AreaChart xs={6} height={300} values={memory}>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} />
|
<Area type="monotone" dataKey="value" stroke={this.memoryColor} fill={this.memoryColor} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
<LineChart xs={6} height={300} values={this.props.traffic}>
|
<LineChart xs={6} height={300} values={traffic}>
|
||||||
<Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} />
|
<Line type="monotone" dataKey="value" stroke={this.trafficColor} dot={false} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
<LineChart xs={6} height={300} values={this.props.memory}>
|
<LineChart xs={6} height={300} values={memory}>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
||||||
<Line type="monotone" dataKey="value" stroke={memoryColor} dot={false} />
|
<Line type="monotone" dataKey="value" stroke={this.memoryColor} dot={false} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
<AreaChart xs={6} height={300} values={this.props.traffic}>
|
<AreaChart xs={6} height={300} values={traffic}>
|
||||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
|
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
|
||||||
<Area type="monotone" dataKey="value" stroke={trafficColor} fill={trafficColor} />
|
<Area type="monotone" dataKey="value" stroke={this.trafficColor} fill={this.trafficColor} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ChartGrid>
|
</ChartGrid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Home.propTypes = {
|
|
||||||
theme: PropTypes.object.isRequired,
|
|
||||||
shouldUpdate: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTheme()(Home);
|
export default withTheme()(Home);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,95 +17,52 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import {TAGS, DRAWER_WIDTH} from "./Common.jsx";
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
import Home from './Home.jsx';
|
|
||||||
|
|
||||||
// ContentSwitch chooses and renders the proper page content.
|
import Home from './Home';
|
||||||
class ContentSwitch extends Component {
|
import {MENU} from './Common';
|
||||||
render() {
|
import type {Content} from '../types/content';
|
||||||
switch(this.props.active) {
|
|
||||||
case TAGS.home.id:
|
|
||||||
return <Home memory={this.props.memory} traffic={this.props.traffic} shouldUpdate={this.props.shouldUpdate} />;
|
|
||||||
case TAGS.chain.id:
|
|
||||||
return null;
|
|
||||||
case TAGS.transactions.id:
|
|
||||||
return null;
|
|
||||||
case TAGS.network.id:
|
|
||||||
// Only for testing.
|
|
||||||
return null;
|
|
||||||
case TAGS.system.id:
|
|
||||||
return null;
|
|
||||||
case TAGS.logs.id:
|
|
||||||
return <div>{this.props.logs.map((log, index) => <div key={index}>{log}</div>)}</div>;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentSwitch.propTypes = {
|
// Styles for the Content component.
|
||||||
active: PropTypes.string.isRequired,
|
|
||||||
shouldUpdate: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// styles contains the styles for the Main component.
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
content: {
|
content: {
|
||||||
width: '100%',
|
flexGrow: 1,
|
||||||
marginLeft: -DRAWER_WIDTH,
|
backgroundColor: theme.palette.background.default,
|
||||||
flexGrow: 1,
|
padding: theme.spacing.unit * 3,
|
||||||
backgroundColor: theme.palette.background.default,
|
overflow: 'auto',
|
||||||
padding: theme.spacing.unit * 3,
|
},
|
||||||
transition: theme.transitions.create('margin', {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
marginTop: 56,
|
|
||||||
overflow: 'auto',
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
content: {
|
|
||||||
height: 'calc(100% - 64px)',
|
|
||||||
marginTop: 64,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contentShift: {
|
|
||||||
marginLeft: 0,
|
|
||||||
transition: theme.transitions.create('margin', {
|
|
||||||
easing: theme.transitions.easing.easeOut,
|
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
// Main renders a component for the page content.
|
classes: Object,
|
||||||
class Main extends Component {
|
active: string,
|
||||||
render() {
|
content: Content,
|
||||||
// The classes property is injected by withStyles().
|
shouldUpdate: Object,
|
||||||
const {classes} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className={classNames(classes.content, this.props.opened && classes.contentShift)}>
|
|
||||||
<ContentSwitch
|
|
||||||
active={this.props.active}
|
|
||||||
memory={this.props.memory}
|
|
||||||
traffic={this.props.traffic}
|
|
||||||
logs={this.props.logs}
|
|
||||||
shouldUpdate={this.props.shouldUpdate}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
opened: PropTypes.bool.isRequired,
|
|
||||||
active: PropTypes.string.isRequired,
|
|
||||||
shouldUpdate: PropTypes.object.isRequired,
|
|
||||||
};
|
};
|
||||||
|
// Main renders the chosen content.
|
||||||
|
class Main extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classes, active, content, shouldUpdate,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
let children = null;
|
||||||
|
switch (active) {
|
||||||
|
case MENU.get('home').id:
|
||||||
|
children = <Home memory={content.home.memory} traffic={content.home.traffic} shouldUpdate={shouldUpdate} />;
|
||||||
|
break;
|
||||||
|
case MENU.get('chain').id:
|
||||||
|
case MENU.get('txpool').id:
|
||||||
|
case MENU.get('network').id:
|
||||||
|
case MENU.get('system').id:
|
||||||
|
children = <div>Work in progress.</div>;
|
||||||
|
break;
|
||||||
|
case MENU.get('logs').id:
|
||||||
|
children = <div>{content.logs.log.map((log, index) => <div key={index}>{log}</div>)}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={classes.content}>{children}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(Main);
|
export default withStyles(styles)(Main);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,92 +17,106 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
import Drawer from 'material-ui/Drawer';
|
|
||||||
import {IconButton} from "material-ui";
|
|
||||||
import List, {ListItem, ListItemText} from 'material-ui/List';
|
|
||||||
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
|
|
||||||
|
|
||||||
import {TAGS, DRAWER_WIDTH} from './Common.jsx';
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import List, {ListItem, ListItemIcon, ListItemText} from 'material-ui/List';
|
||||||
|
import Icon from 'material-ui/Icon';
|
||||||
|
import Transition from 'react-transition-group/Transition';
|
||||||
|
import {Icon as FontAwesome} from 'react-fa';
|
||||||
|
|
||||||
|
import {MENU, DURATION} from './Common';
|
||||||
|
|
||||||
|
// menuDefault is the default style of the menu.
|
||||||
|
const menuDefault = {
|
||||||
|
transition: `margin-left ${DURATION}ms`,
|
||||||
|
};
|
||||||
|
// menuTransition is the additional style of the menu corresponding to the transition's state.
|
||||||
|
const menuTransition = {
|
||||||
|
entered: {marginLeft: -200},
|
||||||
|
};
|
||||||
// Styles for the SideBar component.
|
// Styles for the SideBar component.
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
drawerPaper: {
|
list: {
|
||||||
position: 'relative',
|
background: theme.palette.background.appBar,
|
||||||
height: '100%',
|
},
|
||||||
width: DRAWER_WIDTH,
|
listItem: {
|
||||||
},
|
minWidth: theme.spacing.unit * 3,
|
||||||
drawerHeader: {
|
},
|
||||||
display: 'flex',
|
icon: {
|
||||||
alignItems: 'center',
|
fontSize: theme.spacing.unit * 3,
|
||||||
justifyContent: 'flex-end',
|
},
|
||||||
padding: '0 8px',
|
|
||||||
...theme.mixins.toolbar,
|
|
||||||
transitionDuration: {
|
|
||||||
enter: theme.transitions.duration.enteringScreen,
|
|
||||||
exit: theme.transitions.duration.leavingScreen,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
opened: boolean,
|
||||||
|
changeContent: () => {},
|
||||||
|
};
|
||||||
|
// SideBar renders the sidebar of the dashboard.
|
||||||
|
class SideBar extends Component<Props> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
// SideBar renders a sidebar component.
|
// clickOn contains onClick event functions for the menu items.
|
||||||
class SideBar extends Component {
|
// Instantiate only once, and reuse the existing functions to prevent the creation of
|
||||||
constructor(props) {
|
// new function instances every time the render method is triggered.
|
||||||
super(props);
|
this.clickOn = {};
|
||||||
|
MENU.forEach((menu) => {
|
||||||
|
this.clickOn[menu.id] = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
props.changeContent(menu.id);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// clickOn contains onClick event functions for the menu items.
|
shouldComponentUpdate(nextProps) {
|
||||||
// Instantiate only once, and reuse the existing functions to prevent the creation of
|
return nextProps.opened !== this.props.opened;
|
||||||
// new function instances every time the render method is triggered.
|
}
|
||||||
this.clickOn = {};
|
|
||||||
for(let key in TAGS) {
|
|
||||||
const id = TAGS[key].id;
|
|
||||||
this.clickOn[id] = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
console.log(event.target.key);
|
|
||||||
this.props.changeContent(id);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
menuItems = (transitionState) => {
|
||||||
// The classes property is injected by withStyles().
|
const {classes} = this.props;
|
||||||
const {classes} = this.props;
|
const children = [];
|
||||||
|
MENU.forEach((menu) => {
|
||||||
|
children.push(
|
||||||
|
<ListItem button key={menu.id} onClick={this.clickOn[menu.id]} className={classes.listItem}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon className={classes.icon}>
|
||||||
|
<FontAwesome name={menu.icon} />
|
||||||
|
</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={menu.title}
|
||||||
|
style={{
|
||||||
|
...menuDefault,
|
||||||
|
...menuTransition[transitionState],
|
||||||
|
padding: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
// menu renders the list of the menu items.
|
||||||
<Drawer
|
menu = (transitionState) => {
|
||||||
type="persistent"
|
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||||
classes={{paper: classes.drawerPaper,}}
|
|
||||||
open={this.props.opened}
|
return (
|
||||||
>
|
<div className={classes.list}>
|
||||||
<div>
|
<List>
|
||||||
<div className={classes.drawerHeader}>
|
{this.menuItems(transitionState)}
|
||||||
<IconButton onClick={this.props.close}>
|
</List>
|
||||||
<ChevronLeftIcon />
|
</div>
|
||||||
</IconButton>
|
);
|
||||||
</div>
|
};
|
||||||
<List>
|
|
||||||
{
|
render() {
|
||||||
Object.values(TAGS).map(tag => {
|
return (
|
||||||
return (
|
<Transition mountOnEnter in={this.props.opened} timeout={{enter: DURATION}}>
|
||||||
<ListItem button key={tag.id} onClick={this.clickOn[tag.id]}>
|
{this.menu}
|
||||||
<ListItemText primary={tag.title} />
|
</Transition>
|
||||||
</ListItem>
|
);
|
||||||
);
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SideBar.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
opened: PropTypes.bool.isRequired,
|
|
||||||
close: PropTypes.func.isRequired,
|
|
||||||
changeContent: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(SideBar);
|
export default withStyles(styles)(SideBar);
|
||||||
|
|
|
||||||
25
dashboard/assets/fa-only-woff-loader.js
Normal file
25
dashboard/assets/fa-only-woff-loader.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// fa-only-woff-loader removes the .eot, .ttf, .svg dependencies of the FontAwesome library,
|
||||||
|
// because they produce unused extra blobs.
|
||||||
|
module.exports = function(content) {
|
||||||
|
return content
|
||||||
|
.replace(/src.*url(?!.*url.*(\.eot)).*(\.eot)[^;]*;/,'')
|
||||||
|
.replace(/url(?!.*url.*(\.eot)).*(\.eot)[^,]*,/,'')
|
||||||
|
.replace(/url(?!.*url.*(\.ttf)).*(\.ttf)[^,]*,/,'')
|
||||||
|
.replace(/,[^,]*url(?!.*url.*(\.svg)).*(\.svg)[^;]*;/,';');
|
||||||
|
};
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,22 +17,25 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {hydrate} from 'react-dom';
|
import {render} from 'react-dom';
|
||||||
import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import Dashboard from './components/Dashboard.jsx';
|
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||||
|
import createMuiTheme from 'material-ui/styles/createMuiTheme';
|
||||||
|
|
||||||
|
import Dashboard from './components/Dashboard';
|
||||||
|
|
||||||
// Theme for the dashboard.
|
|
||||||
const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const dashboard = document.getElementById('dashboard');
|
||||||
// Renders the whole dashboard.
|
if (dashboard) {
|
||||||
hydrate(
|
// Renders the whole dashboard.
|
||||||
<MuiThemeProvider theme={theme}>
|
render(
|
||||||
<Dashboard />
|
<MuiThemeProvider theme={theme}>
|
||||||
</MuiThemeProvider>,
|
<Dashboard />
|
||||||
document.getElementById('dashboard')
|
</MuiThemeProvider>,
|
||||||
);
|
dashboard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
6806
dashboard/assets/package-lock.json
generated
Normal file
6806
dashboard/assets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,22 +1,41 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "^8.0.1",
|
"babel-eslint": "^8.0.3",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||||
"babel-preset-stage-0": "^6.24.1",
|
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
||||||
"classnames": "^2.2.5",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"eslint": "^4.5.0",
|
"babel-preset-env": "^1.6.1",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"babel-preset-react": "^6.24.1",
|
||||||
"material-ui": "^1.0.0-beta.18",
|
"babel-preset-stage-0": "^6.24.1",
|
||||||
"material-ui-icons": "^1.0.0-beta.17",
|
"babel-runtime": "^6.26.0",
|
||||||
"path": "^0.12.7",
|
"classnames": "^2.2.5",
|
||||||
"prop-types": "^15.6.0",
|
"css-loader": "^0.28.7",
|
||||||
"recharts": "^1.0.0-beta.0",
|
"eslint": "^4.13.1",
|
||||||
"react": "^16.0.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"react-dom": "^16.0.0",
|
"eslint-loader": "^1.9.0",
|
||||||
"url": "^0.11.0",
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"webpack": "^3.5.5"
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
}
|
"eslint-plugin-react": "^7.5.1",
|
||||||
|
"eslint-plugin-flowtype": "^2.40.1",
|
||||||
|
"file-loader": "^1.1.6",
|
||||||
|
"flow-bin": "^0.61.0",
|
||||||
|
"flow-bin-loader": "^1.0.2",
|
||||||
|
"flow-typed": "^2.2.3",
|
||||||
|
"material-ui": "^1.0.0-beta.24",
|
||||||
|
"material-ui-icons": "^1.0.0-beta.17",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"ramda": "^0.25.0",
|
||||||
|
"react": "^16.2.0",
|
||||||
|
"react-dom": "^16.2.0",
|
||||||
|
"react-fa": "^5.0.0",
|
||||||
|
"react-transition-group": "^2.2.1",
|
||||||
|
"recharts": "^1.0.0-beta.6",
|
||||||
|
"style-loader": "^0.19.1",
|
||||||
|
"url": "^0.11.0",
|
||||||
|
"url-loader": "^0.6.2",
|
||||||
|
"webpack": "^3.10.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,15 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>Go Ethereum Dashboard</title>
|
<title>Go Ethereum Dashboard</title>
|
||||||
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico"/>
|
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico" />
|
||||||
|
<style>
|
||||||
<!-- TODO (kurkomisi): Return to the external libraries to speed up the bundling during development -->
|
::-webkit-scrollbar {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #212121;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="height: 100%; margin: 0">
|
<body style="height: 100%; margin: 0">
|
||||||
<div id="dashboard" style="height: 100%"></div>
|
<div id="dashboard" style="height: 100%"></div>
|
||||||
|
|
|
||||||
53
dashboard/assets/types/content.jsx
Normal file
53
dashboard/assets/types/content.jsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import type {ChartEntry} from './message';
|
||||||
|
|
||||||
|
export type Content = {
|
||||||
|
home: Home,
|
||||||
|
chain: Chain,
|
||||||
|
txpool: TxPool,
|
||||||
|
network: Network,
|
||||||
|
system: System,
|
||||||
|
logs: Logs,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Home = {
|
||||||
|
memory: Array<ChartEntry>,
|
||||||
|
traffic: Array<ChartEntry>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Chain = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TxPool = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Network = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type System = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Logs = {
|
||||||
|
log: Array<string>,
|
||||||
|
};
|
||||||
61
dashboard/assets/types/message.jsx
Normal file
61
dashboard/assets/types/message.jsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export type Message = {
|
||||||
|
home?: HomeMessage,
|
||||||
|
chain?: ChainMessage,
|
||||||
|
txpool?: TxPoolMessage,
|
||||||
|
network?: NetworkMessage,
|
||||||
|
system?: SystemMessage,
|
||||||
|
logs?: LogsMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HomeMessage = {
|
||||||
|
memory?: Chart,
|
||||||
|
traffic?: Chart,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Chart = {
|
||||||
|
history?: Array<ChartEntry>,
|
||||||
|
new?: ChartEntry,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChartEntry = {
|
||||||
|
time: Date,
|
||||||
|
value: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChainMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TxPoolMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NetworkMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SystemMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LogsMessage = {
|
||||||
|
log: string,
|
||||||
|
};
|
||||||
|
|
@ -14,23 +14,61 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './index.jsx',
|
resolve: {
|
||||||
output: {
|
extensions: ['.js', '.jsx'],
|
||||||
path: path.resolve(__dirname, 'public'),
|
},
|
||||||
filename: 'bundle.js',
|
entry: './index',
|
||||||
},
|
output: {
|
||||||
module: {
|
path: path.resolve(__dirname, 'public'),
|
||||||
loaders: [
|
filename: 'bundle.js',
|
||||||
{
|
},
|
||||||
test: /\.jsx$/, // regexp for JSX files
|
plugins: [
|
||||||
loader: 'babel-loader', // The babel configuration is in the package.json.
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
query: {
|
comments: false,
|
||||||
presets: ['env', 'react', 'stage-0']
|
mangle: false,
|
||||||
}
|
beautify: true,
|
||||||
},
|
}),
|
||||||
],
|
],
|
||||||
},
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.jsx$/, // regexp for JSX files
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [ // order: from bottom to top
|
||||||
|
{
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: [ // order: from top to bottom
|
||||||
|
// 'transform-decorators-legacy', // @withStyles, @withTheme
|
||||||
|
'transform-class-properties', // static defaultProps
|
||||||
|
'transform-flow-strip-types',
|
||||||
|
],
|
||||||
|
presets: [ // order: from bottom to top
|
||||||
|
'env',
|
||||||
|
'react',
|
||||||
|
'stage-0',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 'eslint-loader', // show errors not only in the editor, but also in the console
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /font-awesome\.css$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
path.resolve(__dirname, './fa-only-woff-loader.js'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.woff2?$/, // font-awesome icons
|
||||||
|
use: 'url-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
package dashboard
|
package dashboard
|
||||||
|
|
||||||
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets/public/...
|
//go:generate ./assets/node_modules/.bin/webpack --config ./assets/webpack.config.js --context ./assets
|
||||||
|
//go:generate go-bindata -nometadata -o assets.go -prefix assets -nocompress -pkg dashboard assets/public/...
|
||||||
|
//go:generate gofmt -s -w assets.go
|
||||||
|
//go:generate sed -i "s#var _public#//nolint:misspell\\n&#" assets.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -40,7 +43,7 @@ const (
|
||||||
trafficSampleLimit = 200 // Maximum number of traffic data samples
|
trafficSampleLimit = 200 // Maximum number of traffic data samples
|
||||||
)
|
)
|
||||||
|
|
||||||
var nextId uint32 // Next connection id
|
var nextID uint32 // Next connection id
|
||||||
|
|
||||||
// Dashboard contains the dashboard internals.
|
// Dashboard contains the dashboard internals.
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
|
|
@ -48,46 +51,30 @@ type Dashboard struct {
|
||||||
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
conns map[uint32]*client // Currently live websocket connections
|
conns map[uint32]*client // Currently live websocket connections
|
||||||
charts charts // The collected data samples to plot
|
charts *HomeMessage
|
||||||
lock sync.RWMutex // Lock protecting the dashboard's internals
|
lock sync.RWMutex // Lock protecting the dashboard's internals
|
||||||
|
|
||||||
quit chan chan error // Channel used for graceful exit
|
quit chan chan error // Channel used for graceful exit
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// message embraces the data samples of a client message.
|
|
||||||
type message struct {
|
|
||||||
History *charts `json:"history,omitempty"` // Past data samples
|
|
||||||
Memory *chartEntry `json:"memory,omitempty"` // One memory sample
|
|
||||||
Traffic *chartEntry `json:"traffic,omitempty"` // One traffic sample
|
|
||||||
Log string `json:"log,omitempty"` // One log
|
|
||||||
}
|
|
||||||
|
|
||||||
// client represents active websocket connection with a remote browser.
|
// client represents active websocket connection with a remote browser.
|
||||||
type client struct {
|
type client struct {
|
||||||
conn *websocket.Conn // Particular live websocket connection
|
conn *websocket.Conn // Particular live websocket connection
|
||||||
msg chan message // Message queue for the update messages
|
msg chan Message // Message queue for the update messages
|
||||||
logger log.Logger // Logger for the particular live websocket connection
|
logger log.Logger // Logger for the particular live websocket connection
|
||||||
}
|
}
|
||||||
|
|
||||||
// charts contains the collected data samples.
|
|
||||||
type charts struct {
|
|
||||||
Memory []*chartEntry `json:"memorySamples,omitempty"`
|
|
||||||
Traffic []*chartEntry `json:"trafficSamples,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// chartEntry represents one data sample
|
|
||||||
type chartEntry struct {
|
|
||||||
Time time.Time `json:"time,omitempty"`
|
|
||||||
Value float64 `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new dashboard instance with the given configuration.
|
// New creates a new dashboard instance with the given configuration.
|
||||||
func New(config *Config) (*Dashboard, error) {
|
func New(config *Config) (*Dashboard, error) {
|
||||||
return &Dashboard{
|
return &Dashboard{
|
||||||
conns: make(map[uint32]*client),
|
conns: make(map[uint32]*client),
|
||||||
config: config,
|
config: config,
|
||||||
quit: make(chan chan error),
|
quit: make(chan chan error),
|
||||||
|
charts: &HomeMessage{
|
||||||
|
Memory: &Chart{},
|
||||||
|
Traffic: &Chart{},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,13 +170,13 @@ func (db *Dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// apiHandler handles requests for the dashboard.
|
// apiHandler handles requests for the dashboard.
|
||||||
func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
id := atomic.AddUint32(&nextId, 1)
|
id := atomic.AddUint32(&nextID, 1)
|
||||||
client := &client{
|
client := &client{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
msg: make(chan message, 128),
|
msg: make(chan Message, 128),
|
||||||
logger: log.New("id", id),
|
logger: log.New("id", id),
|
||||||
}
|
}
|
||||||
done := make(chan struct{}) // Buffered channel as sender may exit early
|
done := make(chan struct{})
|
||||||
|
|
||||||
// Start listening for messages to send.
|
// Start listening for messages to send.
|
||||||
db.wg.Add(1)
|
db.wg.Add(1)
|
||||||
|
|
@ -210,8 +197,15 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Send the past data.
|
// Send the past data.
|
||||||
client.msg <- message{
|
client.msg <- Message{
|
||||||
History: &db.charts,
|
Home: &HomeMessage{
|
||||||
|
Memory: &Chart{
|
||||||
|
History: db.charts.Memory.History,
|
||||||
|
},
|
||||||
|
Traffic: &Chart{
|
||||||
|
History: db.charts.Traffic.History,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
// Start tracking the connection and drop at connection loss.
|
// Start tracking the connection and drop at connection loss.
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
|
|
@ -245,29 +239,34 @@ func (db *Dashboard) collectData() {
|
||||||
inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
||||||
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
|
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
memory := &chartEntry{
|
memory := &ChartEntry{
|
||||||
Time: now,
|
Time: now,
|
||||||
Value: memoryInUse,
|
Value: memoryInUse,
|
||||||
}
|
}
|
||||||
traffic := &chartEntry{
|
traffic := &ChartEntry{
|
||||||
Time: now,
|
Time: now,
|
||||||
Value: inboundTraffic,
|
Value: inboundTraffic,
|
||||||
}
|
}
|
||||||
// Remove the first elements in case the samples' amount exceeds the limit.
|
|
||||||
first := 0
|
first := 0
|
||||||
if len(db.charts.Memory) == memorySampleLimit {
|
if len(db.charts.Memory.History) == memorySampleLimit {
|
||||||
first = 1
|
first = 1
|
||||||
}
|
}
|
||||||
db.charts.Memory = append(db.charts.Memory[first:], memory)
|
db.charts.Memory.History = append(db.charts.Memory.History[first:], memory)
|
||||||
first = 0
|
first = 0
|
||||||
if len(db.charts.Traffic) == trafficSampleLimit {
|
if len(db.charts.Traffic.History) == trafficSampleLimit {
|
||||||
first = 1
|
first = 1
|
||||||
}
|
}
|
||||||
db.charts.Traffic = append(db.charts.Traffic[first:], traffic)
|
db.charts.Traffic.History = append(db.charts.Traffic.History[first:], traffic)
|
||||||
|
|
||||||
db.sendToAll(&message{
|
db.sendToAll(&Message{
|
||||||
Memory: memory,
|
Home: &HomeMessage{
|
||||||
Traffic: traffic,
|
Memory: &Chart{
|
||||||
|
New: memory,
|
||||||
|
},
|
||||||
|
Traffic: &Chart{
|
||||||
|
New: traffic,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,6 +276,7 @@ func (db *Dashboard) collectData() {
|
||||||
func (db *Dashboard) collectLogs() {
|
func (db *Dashboard) collectLogs() {
|
||||||
defer db.wg.Done()
|
defer db.wg.Done()
|
||||||
|
|
||||||
|
id := 1
|
||||||
// TODO (kurkomisi): log collection comes here.
|
// TODO (kurkomisi): log collection comes here.
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -284,15 +284,18 @@ func (db *Dashboard) collectLogs() {
|
||||||
errc <- nil
|
errc <- nil
|
||||||
return
|
return
|
||||||
case <-time.After(db.config.Refresh / 2):
|
case <-time.After(db.config.Refresh / 2):
|
||||||
db.sendToAll(&message{
|
db.sendToAll(&Message{
|
||||||
Log: "This is a fake log.",
|
Logs: &LogsMessage{
|
||||||
|
Log: fmt.Sprintf("%-4d: This is a fake log.", id),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
id++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendToAll sends the given message to the active dashboards.
|
// sendToAll sends the given message to the active dashboards.
|
||||||
func (db *Dashboard) sendToAll(msg *message) {
|
func (db *Dashboard) sendToAll(msg *Message) {
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
for _, c := range db.conns {
|
for _, c := range db.conns {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
63
dashboard/message.go
Normal file
63
dashboard/message.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dashboard
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Home *HomeMessage `json:"home,omitempty"`
|
||||||
|
Chain *ChainMessage `json:"chain,omitempty"`
|
||||||
|
TxPool *TxPoolMessage `json:"txpool,omitempty"`
|
||||||
|
Network *NetworkMessage `json:"network,omitempty"`
|
||||||
|
System *SystemMessage `json:"system,omitempty"`
|
||||||
|
Logs *LogsMessage `json:"logs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HomeMessage struct {
|
||||||
|
Memory *Chart `json:"memory,omitempty"`
|
||||||
|
Traffic *Chart `json:"traffic,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chart struct {
|
||||||
|
History []*ChartEntry `json:"history,omitempty"`
|
||||||
|
New *ChartEntry `json:"new,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartEntry struct {
|
||||||
|
Time time.Time `json:"time,omitempty"`
|
||||||
|
Value float64 `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxPoolMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogsMessage struct {
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
}
|
||||||
253
eth/api.go
253
eth/api.go
|
|
@ -17,24 +17,19 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/miner"
|
"github.com/ethereum/go-ethereum/miner"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
|
@ -43,8 +38,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultTraceTimeout = 5 * time.Second
|
|
||||||
|
|
||||||
// PublicEthereumAPI provides an API to access Ethereum full node-related
|
// PublicEthereumAPI provides an API to access Ethereum full node-related
|
||||||
// information.
|
// information.
|
||||||
type PublicEthereumAPI struct {
|
type PublicEthereumAPI struct {
|
||||||
|
|
@ -348,238 +341,6 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug
|
||||||
return &PrivateDebugAPI{config: config, eth: eth}
|
return &PrivateDebugAPI{config: config, eth: eth}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockTraceResult is the returned value when replaying a block to check for
|
|
||||||
// consensus results and full VM trace logs for all included transactions.
|
|
||||||
type BlockTraceResult struct {
|
|
||||||
Validated bool `json:"validated"`
|
|
||||||
StructLogs []ethapi.StructLogRes `json:"structLogs"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceArgs holds extra parameters to trace functions
|
|
||||||
type TraceArgs struct {
|
|
||||||
*vm.LogConfig
|
|
||||||
Tracer *string
|
|
||||||
Timeout *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceBlock processes the given block'api RLP but does not import the block in to
|
|
||||||
// the chain.
|
|
||||||
func (api *PrivateDebugAPI) TraceBlock(blockRlp []byte, config *vm.LogConfig) BlockTraceResult {
|
|
||||||
var block types.Block
|
|
||||||
err := rlp.Decode(bytes.NewReader(blockRlp), &block)
|
|
||||||
if err != nil {
|
|
||||||
return BlockTraceResult{Error: fmt.Sprintf("could not decode block: %v", err)}
|
|
||||||
}
|
|
||||||
|
|
||||||
validated, logs, err := api.traceBlock(&block, config)
|
|
||||||
return BlockTraceResult{
|
|
||||||
Validated: validated,
|
|
||||||
StructLogs: ethapi.FormatLogs(logs),
|
|
||||||
Error: formatError(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceBlockFromFile loads the block'api RLP from the given file name and attempts to
|
|
||||||
// process it but does not import the block in to the chain.
|
|
||||||
func (api *PrivateDebugAPI) TraceBlockFromFile(file string, config *vm.LogConfig) BlockTraceResult {
|
|
||||||
blockRlp, err := ioutil.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return BlockTraceResult{Error: fmt.Sprintf("could not read file: %v", err)}
|
|
||||||
}
|
|
||||||
return api.TraceBlock(blockRlp, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceBlockByNumber processes the block by canonical block number.
|
|
||||||
func (api *PrivateDebugAPI) TraceBlockByNumber(blockNr rpc.BlockNumber, config *vm.LogConfig) BlockTraceResult {
|
|
||||||
// Fetch the block that we aim to reprocess
|
|
||||||
var block *types.Block
|
|
||||||
switch blockNr {
|
|
||||||
case rpc.PendingBlockNumber:
|
|
||||||
// Pending block is only known by the miner
|
|
||||||
block = api.eth.miner.PendingBlock()
|
|
||||||
case rpc.LatestBlockNumber:
|
|
||||||
block = api.eth.blockchain.CurrentBlock()
|
|
||||||
default:
|
|
||||||
block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
|
|
||||||
}
|
|
||||||
|
|
||||||
if block == nil {
|
|
||||||
return BlockTraceResult{Error: fmt.Sprintf("block #%d not found", blockNr)}
|
|
||||||
}
|
|
||||||
|
|
||||||
validated, logs, err := api.traceBlock(block, config)
|
|
||||||
return BlockTraceResult{
|
|
||||||
Validated: validated,
|
|
||||||
StructLogs: ethapi.FormatLogs(logs),
|
|
||||||
Error: formatError(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceBlockByHash processes the block by hash.
|
|
||||||
func (api *PrivateDebugAPI) TraceBlockByHash(hash common.Hash, config *vm.LogConfig) BlockTraceResult {
|
|
||||||
// Fetch the block that we aim to reprocess
|
|
||||||
block := api.eth.BlockChain().GetBlockByHash(hash)
|
|
||||||
if block == nil {
|
|
||||||
return BlockTraceResult{Error: fmt.Sprintf("block #%x not found", hash)}
|
|
||||||
}
|
|
||||||
|
|
||||||
validated, logs, err := api.traceBlock(block, config)
|
|
||||||
return BlockTraceResult{
|
|
||||||
Validated: validated,
|
|
||||||
StructLogs: ethapi.FormatLogs(logs),
|
|
||||||
Error: formatError(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// traceBlock processes the given block but does not save the state.
|
|
||||||
func (api *PrivateDebugAPI) traceBlock(block *types.Block, logConfig *vm.LogConfig) (bool, []vm.StructLog, error) {
|
|
||||||
// Validate and reprocess the block
|
|
||||||
var (
|
|
||||||
blockchain = api.eth.BlockChain()
|
|
||||||
validator = blockchain.Validator()
|
|
||||||
processor = blockchain.Processor()
|
|
||||||
)
|
|
||||||
|
|
||||||
structLogger := vm.NewStructLogger(logConfig)
|
|
||||||
|
|
||||||
config := vm.Config{
|
|
||||||
Debug: true,
|
|
||||||
Tracer: structLogger,
|
|
||||||
}
|
|
||||||
if err := api.eth.engine.VerifyHeader(blockchain, block.Header(), true); err != nil {
|
|
||||||
return false, structLogger.StructLogs(), err
|
|
||||||
}
|
|
||||||
statedb, err := blockchain.StateAt(blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1).Root())
|
|
||||||
if err != nil {
|
|
||||||
return false, structLogger.StructLogs(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
receipts, _, usedGas, err := processor.Process(block, statedb, config)
|
|
||||||
if err != nil {
|
|
||||||
return false, structLogger.StructLogs(), err
|
|
||||||
}
|
|
||||||
if err := validator.ValidateState(block, blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1), statedb, receipts, usedGas); err != nil {
|
|
||||||
return false, structLogger.StructLogs(), err
|
|
||||||
}
|
|
||||||
return true, structLogger.StructLogs(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatError formats a Go error into either an empty string or the data content
|
|
||||||
// of the error itself.
|
|
||||||
func formatError(err error) string {
|
|
||||||
if err == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
type timeoutError struct{}
|
|
||||||
|
|
||||||
func (t *timeoutError) Error() string {
|
|
||||||
return "Execution time exceeded"
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceTransaction returns the structured logs created during the execution of EVM
|
|
||||||
// and returns them as a JSON object.
|
|
||||||
func (api *PrivateDebugAPI) TraceTransaction(ctx context.Context, txHash common.Hash, config *TraceArgs) (interface{}, error) {
|
|
||||||
var tracer vm.Tracer
|
|
||||||
if config != nil && config.Tracer != nil {
|
|
||||||
timeout := defaultTraceTimeout
|
|
||||||
if config.Timeout != nil {
|
|
||||||
var err error
|
|
||||||
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if tracer, err = ethapi.NewJavascriptTracer(*config.Tracer); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle timeouts and RPC cancellations
|
|
||||||
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
|
|
||||||
go func() {
|
|
||||||
<-deadlineCtx.Done()
|
|
||||||
tracer.(*ethapi.JavascriptTracer).Stop(&timeoutError{})
|
|
||||||
}()
|
|
||||||
defer cancel()
|
|
||||||
} else if config == nil {
|
|
||||||
tracer = vm.NewStructLogger(nil)
|
|
||||||
} else {
|
|
||||||
tracer = vm.NewStructLogger(config.LogConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the tx from the chain and the containing block
|
|
||||||
tx, blockHash, _, txIndex := core.GetTransaction(api.eth.ChainDb(), txHash)
|
|
||||||
if tx == nil {
|
|
||||||
return nil, fmt.Errorf("transaction %x not found", txHash)
|
|
||||||
}
|
|
||||||
msg, context, statedb, err := api.computeTxEnv(blockHash, int(txIndex))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the transaction with tracing enabled.
|
|
||||||
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{Debug: true, Tracer: tracer})
|
|
||||||
ret, gas, failed, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("tracing failed: %v", err)
|
|
||||||
}
|
|
||||||
switch tracer := tracer.(type) {
|
|
||||||
case *vm.StructLogger:
|
|
||||||
return ðapi.ExecutionResult{
|
|
||||||
Gas: gas,
|
|
||||||
Failed: failed,
|
|
||||||
ReturnValue: fmt.Sprintf("%x", ret),
|
|
||||||
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
|
||||||
}, nil
|
|
||||||
case *ethapi.JavascriptTracer:
|
|
||||||
return tracer.GetResult()
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("bad tracer type %T", tracer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeTxEnv returns the execution environment of a certain transaction.
|
|
||||||
func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int) (core.Message, vm.Context, *state.StateDB, error) {
|
|
||||||
// Create the parent state.
|
|
||||||
block := api.eth.BlockChain().GetBlockByHash(blockHash)
|
|
||||||
if block == nil {
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("block %x not found", blockHash)
|
|
||||||
}
|
|
||||||
parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
|
|
||||||
if parent == nil {
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("block parent %x not found", block.ParentHash())
|
|
||||||
}
|
|
||||||
statedb, err := api.eth.BlockChain().StateAt(parent.Root())
|
|
||||||
if err != nil {
|
|
||||||
return nil, vm.Context{}, nil, err
|
|
||||||
}
|
|
||||||
txs := block.Transactions()
|
|
||||||
|
|
||||||
// Recompute transactions up to the target index.
|
|
||||||
signer := types.MakeSigner(api.config, block.Number())
|
|
||||||
for idx, tx := range txs {
|
|
||||||
// Assemble the transaction call message
|
|
||||||
msg, _ := tx.AsMessage(signer)
|
|
||||||
context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain(), nil)
|
|
||||||
if idx == txIndex {
|
|
||||||
return msg, context, statedb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{})
|
|
||||||
gp := new(core.GasPool).AddGas(tx.Gas())
|
|
||||||
_, _, _, err := core.ApplyMessage(vmenv, msg, gp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
|
|
||||||
}
|
|
||||||
statedb.DeleteSuicides()
|
|
||||||
}
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
|
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
|
||||||
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||||||
db := core.PreimageTable(api.eth.ChainDb())
|
db := core.PreimageTable(api.eth.ChainDb())
|
||||||
|
|
@ -607,7 +368,7 @@ type storageEntry struct {
|
||||||
|
|
||||||
// StorageRangeAt returns the storage at the given block height and transaction index.
|
// StorageRangeAt returns the storage at the given block height and transaction index.
|
||||||
func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
|
func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
|
||||||
_, _, statedb, err := api.computeTxEnv(blockHash, txIndex)
|
_, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return StorageRangeResult{}, err
|
return StorageRangeResult{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -615,14 +376,18 @@ func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common
|
||||||
if st == nil {
|
if st == nil {
|
||||||
return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
|
return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress)
|
||||||
}
|
}
|
||||||
return storageRangeAt(st, keyStart, maxResult), nil
|
return storageRangeAt(st, keyStart, maxResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
func storageRangeAt(st state.Trie, start []byte, maxResult int) StorageRangeResult {
|
func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeResult, error) {
|
||||||
it := trie.NewIterator(st.NodeIterator(start))
|
it := trie.NewIterator(st.NodeIterator(start))
|
||||||
result := StorageRangeResult{Storage: storageMap{}}
|
result := StorageRangeResult{Storage: storageMap{}}
|
||||||
for i := 0; i < maxResult && it.Next(); i++ {
|
for i := 0; i < maxResult && it.Next(); i++ {
|
||||||
e := storageEntry{Value: common.BytesToHash(it.Value)}
|
_, content, _, err := rlp.Split(it.Value)
|
||||||
|
if err != nil {
|
||||||
|
return StorageRangeResult{}, err
|
||||||
|
}
|
||||||
|
e := storageEntry{Value: common.BytesToHash(content)}
|
||||||
if preimage := st.GetKey(it.Key); preimage != nil {
|
if preimage := st.GetKey(it.Key); preimage != nil {
|
||||||
preimage := common.BytesToHash(preimage)
|
preimage := common.BytesToHash(preimage)
|
||||||
e.Key = &preimage
|
e.Key = &preimage
|
||||||
|
|
@ -634,7 +399,7 @@ func storageRangeAt(st state.Trie, start []byte, maxResult int) StorageRangeResu
|
||||||
next := common.BytesToHash(it.Key)
|
next := common.BytesToHash(it.Key)
|
||||||
result.NextKey = &next
|
result.NextKey = &next
|
||||||
}
|
}
|
||||||
return result
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetModifiedAccountsByumber returns all accounts that have changed between the
|
// GetModifiedAccountsByumber returns all accounts that have changed between the
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,10 @@ func TestStorageRangeAt(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
result := storageRangeAt(state.StorageTrie(addr), test.start, test.limit)
|
result, err := storageRangeAt(state.StorageTrie(addr), test.start, test.limit)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
if !reflect.DeepEqual(result, test.want) {
|
if !reflect.DeepEqual(result, test.want) {
|
||||||
t.Fatalf("wrong result for range 0x%x.., limit %d:\ngot %s\nwant %s",
|
t.Fatalf("wrong result for range 0x%x.., limit %d:\ngot %s\nwant %s",
|
||||||
test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want))
|
test.start, test.limit, dumper.Sdump(result), dumper.Sdump(&test.want))
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue