This commit is contained in:
LLLeon 2017-12-24 16:23:04 +08:00
commit cde27a9f0b
242 changed files with 164014 additions and 2870 deletions

1
.gitignore vendored
View file

@ -35,6 +35,7 @@ profile.cov
.idea
# dashboard
/dashboard/assets/flow-typed
/dashboard/assets/node_modules
/dashboard/assets/stats.json
/dashboard/assets/public/bundle.js

View file

@ -8,7 +8,6 @@ matrix:
sudo: required
go: 1.7.x
script:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
@ -20,7 +19,6 @@ matrix:
sudo: required
go: 1.8.x
script:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
@ -33,7 +31,6 @@ matrix:
sudo: required
go: 1.9.x
script:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
@ -42,7 +39,6 @@ matrix:
- os: osx
go: 1.9.x
sudo: required
script:
- brew update
- brew install caskroom/cask/brew-cask
@ -53,15 +49,12 @@ matrix:
# This builder only tests code linters on latest version of Go
- os: linux
dist: trusty
sudo: required
go: 1.9.x
env:
- lint
git:
submodules: false # avoid cloning ethereum/tests
script:
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install fuse
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
- go run build/ci.go lint
# This builder does the Ubuntu PPA and Linux Azure uploads
@ -72,6 +65,8 @@ matrix:
env:
- ubuntu-ppa
- azure-linux
git:
submodules: false # avoid cloning ethereum/tests
addons:
apt:
packages:
@ -104,12 +99,13 @@ matrix:
# This builder does the Linux Azure MIPS xgo uploads
- os: linux
dist: trusty
sudo: required
services:
- docker
go: 1.9.x
env:
- azure-linux-mips
git:
submodules: false # avoid cloning ethereum/tests
script:
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
@ -146,6 +142,8 @@ matrix:
env:
- azure-android
- maven-android
git:
submodules: false # avoid cloning ethereum/tests
before_install:
- curl https://storage.googleapis.com/golang/go1.9.2.linux-amd64.tar.gz | tar -xz
- export PATH=`pwd`/go/bin:$PATH
@ -169,6 +167,8 @@ matrix:
- azure-osx
- azure-ios
- cocoapods-ios
git:
submodules: false # avoid cloning ethereum/tests
script:
- go run build/ci.go install
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
@ -193,15 +193,11 @@ matrix:
go: 1.9.x
env:
- azure-purge
git:
submodules: false # avoid cloning ethereum/tests
script:
- go run build/ci.go purge -store gethstore/builds -days 14
install:
- go get golang.org/x/tools/cmd/cover
script:
- go run build/ci.go install
- go run build/ci.go test -coverage
notifications:
webhooks:
urls:

View file

@ -17,6 +17,7 @@
package abi
import (
"bytes"
"encoding/json"
"fmt"
"io"
@ -50,51 +51,47 @@ func JSON(reader io.Reader) (ABI, error) {
// methods string signature. (signature = baz(uint32,string32))
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
// Fetch the ABI of the requested method
var method Method
if name == "" {
method = abi.Constructor
} else {
m, exist := abi.Methods[name]
// constructor
arguments, err := abi.Constructor.Inputs.Pack(args...)
if err != nil {
return nil, err
}
return arguments, nil
}
method, exist := abi.Methods[name]
if !exist {
return nil, fmt.Errorf("method '%s' not found", name)
}
method = m
}
arguments, err := method.pack(args...)
arguments, err := method.Inputs.Pack(args...)
if err != nil {
return nil, err
}
// Pack up the method ID too if not a constructor and return
if name == "" {
return arguments, nil
}
return append(method.Id(), arguments...), nil
}
// Unpack output in v according to the abi specification
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
if err = bytesAreProper(output); err != nil {
return err
if len(output) == 0 {
return fmt.Errorf("abi: unmarshalling empty output")
}
// since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event
var unpack unpacker
if method, ok := abi.Methods[name]; ok {
unpack = method
if len(output)%32 != 0 {
return fmt.Errorf("abi: improperly formatted output")
}
return method.Outputs.Unpack(v, output)
} else if event, ok := abi.Events[name]; ok {
unpack = event
} else {
return fmt.Errorf("abi: could not locate named method or event.")
return event.Inputs.Unpack(v, output)
}
// requires a struct to unpack into for a tuple return...
if unpack.isTupleReturn() {
return unpack.tupleUnpack(v, output)
}
return unpack.singleUnpack(v, output)
return fmt.Errorf("abi: could not locate named method or event")
}
// UnmarshalJSON implements json.Unmarshaler interface
func (abi *ABI) UnmarshalJSON(data []byte) error {
var fields []struct {
Type string
@ -137,3 +134,14 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
return nil
}
// MethodById looks up a method by the 4-byte id
// returns nil if none found
func (abi *ABI) MethodById(sigdata []byte) *Method {
for _, method := range abi.Methods {
if bytes.Equal(method.Id(), sigdata[:4]) {
return &method
}
}
return nil
}

View file

@ -18,13 +18,15 @@ package abi
import (
"bytes"
"encoding/hex"
"fmt"
"log"
"math/big"
"reflect"
"strings"
"testing"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
@ -74,9 +76,24 @@ func TestReader(t *testing.T) {
}
// deep equal fails for some reason
t.Skip()
if !reflect.DeepEqual(abi, exp) {
t.Errorf("\nabi: %v\ndoes not match exp: %v", abi, exp)
for name, expM := range exp.Methods {
gotM, exist := abi.Methods[name]
if !exist {
t.Errorf("Missing expected method %v", name)
}
if !reflect.DeepEqual(gotM, expM) {
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
}
}
for name, gotM := range abi.Methods {
expM, exist := exp.Methods[name]
if !exist {
t.Errorf("Found extra method %v", name)
}
if !reflect.DeepEqual(gotM, expM) {
t.Errorf("\nGot abi method: \n%v\ndoes not match expected method\n%v", gotM, expM)
}
}
}
@ -348,6 +365,188 @@ func TestInputVariableInputLength(t *testing.T) {
}
}
func TestInputFixedArrayAndVariableInputLength(t *testing.T) {
const definition = `[
{ "type" : "function", "name" : "fixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "fixedArrBytes", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" }, { "name" : "fixedArr", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "mixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr", "type": "uint256[2]" }, { "name" : "dynArr", "type": "uint256[]" } ] },
{ "type" : "function", "name" : "doubleFixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "fixedArr2", "type": "uint256[3]" } ] },
{ "type" : "function", "name" : "multipleMixedArrStr", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" }, { "name" : "fixedArr1", "type": "uint256[2]" }, { "name" : "dynArr", "type" : "uint256[]" }, { "name" : "fixedArr2", "type" : "uint256[3]" } ] }
]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Error(err)
}
// test string, fixed array uint256[2]
strin := "hello world"
arrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
fixedArrStrPack, err := abi.Pack("fixedArrStr", strin, arrin)
if err != nil {
t.Error(err)
}
// generate expected output
offset := make([]byte, 32)
offset[31] = 96
length := make([]byte, 32)
length[31] = byte(len(strin))
strvalue := common.RightPadBytes([]byte(strin), 32)
arrinvalue1 := common.LeftPadBytes(arrin[0].Bytes(), 32)
arrinvalue2 := common.LeftPadBytes(arrin[1].Bytes(), 32)
exp := append(offset, arrinvalue1...)
exp = append(exp, arrinvalue2...)
exp = append(exp, append(length, strvalue...)...)
// ignore first 4 bytes of the output. This is the function identifier
fixedArrStrPack = fixedArrStrPack[4:]
if !bytes.Equal(fixedArrStrPack, exp) {
t.Errorf("expected %x, got %x\n", exp, fixedArrStrPack)
}
// test byte array, fixed array uint256[2]
bytesin := []byte(strin)
arrin = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
fixedArrBytesPack, err := abi.Pack("fixedArrBytes", bytesin, arrin)
if err != nil {
t.Error(err)
}
// generate expected output
offset = make([]byte, 32)
offset[31] = 96
length = make([]byte, 32)
length[31] = byte(len(strin))
strvalue = common.RightPadBytes([]byte(strin), 32)
arrinvalue1 = common.LeftPadBytes(arrin[0].Bytes(), 32)
arrinvalue2 = common.LeftPadBytes(arrin[1].Bytes(), 32)
exp = append(offset, arrinvalue1...)
exp = append(exp, arrinvalue2...)
exp = append(exp, append(length, strvalue...)...)
// ignore first 4 bytes of the output. This is the function identifier
fixedArrBytesPack = fixedArrBytesPack[4:]
if !bytes.Equal(fixedArrBytesPack, exp) {
t.Errorf("expected %x, got %x\n", exp, fixedArrBytesPack)
}
// test string, fixed array uint256[2], dynamic array uint256[]
strin = "hello world"
fixedarrin := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
dynarrin := []*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
mixedArrStrPack, err := abi.Pack("mixedArrStr", strin, fixedarrin, dynarrin)
if err != nil {
t.Error(err)
}
// generate expected output
stroffset := make([]byte, 32)
stroffset[31] = 128
strlength := make([]byte, 32)
strlength[31] = byte(len(strin))
strvalue = common.RightPadBytes([]byte(strin), 32)
fixedarrinvalue1 := common.LeftPadBytes(fixedarrin[0].Bytes(), 32)
fixedarrinvalue2 := common.LeftPadBytes(fixedarrin[1].Bytes(), 32)
dynarroffset := make([]byte, 32)
dynarroffset[31] = byte(160 + ((len(strin)/32)+1)*32)
dynarrlength := make([]byte, 32)
dynarrlength[31] = byte(len(dynarrin))
dynarrinvalue1 := common.LeftPadBytes(dynarrin[0].Bytes(), 32)
dynarrinvalue2 := common.LeftPadBytes(dynarrin[1].Bytes(), 32)
dynarrinvalue3 := common.LeftPadBytes(dynarrin[2].Bytes(), 32)
exp = append(stroffset, fixedarrinvalue1...)
exp = append(exp, fixedarrinvalue2...)
exp = append(exp, dynarroffset...)
exp = append(exp, append(strlength, strvalue...)...)
dynarrarg := append(dynarrlength, dynarrinvalue1...)
dynarrarg = append(dynarrarg, dynarrinvalue2...)
dynarrarg = append(dynarrarg, dynarrinvalue3...)
exp = append(exp, dynarrarg...)
// ignore first 4 bytes of the output. This is the function identifier
mixedArrStrPack = mixedArrStrPack[4:]
if !bytes.Equal(mixedArrStrPack, exp) {
t.Errorf("expected %x, got %x\n", exp, mixedArrStrPack)
}
// test string, fixed array uint256[2], fixed array uint256[3]
strin = "hello world"
fixedarrin1 := [2]*big.Int{big.NewInt(1), big.NewInt(2)}
fixedarrin2 := [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
doubleFixedArrStrPack, err := abi.Pack("doubleFixedArrStr", strin, fixedarrin1, fixedarrin2)
if err != nil {
t.Error(err)
}
// generate expected output
stroffset = make([]byte, 32)
stroffset[31] = 192
strlength = make([]byte, 32)
strlength[31] = byte(len(strin))
strvalue = common.RightPadBytes([]byte(strin), 32)
fixedarrin1value1 := common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
fixedarrin1value2 := common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
fixedarrin2value1 := common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
fixedarrin2value2 := common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
fixedarrin2value3 := common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
exp = append(stroffset, fixedarrin1value1...)
exp = append(exp, fixedarrin1value2...)
exp = append(exp, fixedarrin2value1...)
exp = append(exp, fixedarrin2value2...)
exp = append(exp, fixedarrin2value3...)
exp = append(exp, append(strlength, strvalue...)...)
// ignore first 4 bytes of the output. This is the function identifier
doubleFixedArrStrPack = doubleFixedArrStrPack[4:]
if !bytes.Equal(doubleFixedArrStrPack, exp) {
t.Errorf("expected %x, got %x\n", exp, doubleFixedArrStrPack)
}
// test string, fixed array uint256[2], dynamic array uint256[], fixed array uint256[3]
strin = "hello world"
fixedarrin1 = [2]*big.Int{big.NewInt(1), big.NewInt(2)}
dynarrin = []*big.Int{big.NewInt(1), big.NewInt(2)}
fixedarrin2 = [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)}
multipleMixedArrStrPack, err := abi.Pack("multipleMixedArrStr", strin, fixedarrin1, dynarrin, fixedarrin2)
if err != nil {
t.Error(err)
}
// generate expected output
stroffset = make([]byte, 32)
stroffset[31] = 224
strlength = make([]byte, 32)
strlength[31] = byte(len(strin))
strvalue = common.RightPadBytes([]byte(strin), 32)
fixedarrin1value1 = common.LeftPadBytes(fixedarrin1[0].Bytes(), 32)
fixedarrin1value2 = common.LeftPadBytes(fixedarrin1[1].Bytes(), 32)
dynarroffset = U256(big.NewInt(int64(256 + ((len(strin)/32)+1)*32)))
dynarrlength = make([]byte, 32)
dynarrlength[31] = byte(len(dynarrin))
dynarrinvalue1 = common.LeftPadBytes(dynarrin[0].Bytes(), 32)
dynarrinvalue2 = common.LeftPadBytes(dynarrin[1].Bytes(), 32)
fixedarrin2value1 = common.LeftPadBytes(fixedarrin2[0].Bytes(), 32)
fixedarrin2value2 = common.LeftPadBytes(fixedarrin2[1].Bytes(), 32)
fixedarrin2value3 = common.LeftPadBytes(fixedarrin2[2].Bytes(), 32)
exp = append(stroffset, fixedarrin1value1...)
exp = append(exp, fixedarrin1value2...)
exp = append(exp, dynarroffset...)
exp = append(exp, fixedarrin2value1...)
exp = append(exp, fixedarrin2value2...)
exp = append(exp, fixedarrin2value3...)
exp = append(exp, append(strlength, strvalue...)...)
dynarrarg = append(dynarrlength, dynarrinvalue1...)
dynarrarg = append(dynarrarg, dynarrinvalue2...)
exp = append(exp, dynarrarg...)
// ignore first 4 bytes of the output. This is the function identifier
multipleMixedArrStrPack = multipleMixedArrStrPack[4:]
if !bytes.Equal(multipleMixedArrStrPack, exp) {
t.Errorf("expected %x, got %x\n", exp, multipleMixedArrStrPack)
}
}
func TestDefaultFunctionParsing(t *testing.T) {
const definition = `[{ "name" : "balance" }]`
@ -418,3 +617,82 @@ func TestBareEvents(t *testing.T) {
}
}
}
// TestUnpackEvent is based on this contract:
// contract T {
// event received(address sender, uint amount, bytes memo);
// function receive(bytes memo) external payable {
// received(msg.sender, msg.value, memo);
// }
// }
// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt:
// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]}
func TestUnpackEvent(t *testing.T) {
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
t.Fatal(err)
}
const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
data, err := hex.DecodeString(hexdata)
if err != nil {
t.Fatal(err)
}
if len(data)%32 == 0 {
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
}
type ReceivedEvent struct {
Address common.Address
Amount *big.Int
Memo []byte
}
var ev ReceivedEvent
err = abi.Unpack(&ev, "received", data)
if err != nil {
t.Error(err)
} else {
t.Logf("len(data): %d; received event: %+v", len(data), ev)
}
}
func TestABI_MethodById(t *testing.T) {
const abiJSON = `[
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
{"type":"event","name":"received","anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}]},
{"type":"function","name":"fixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"}]},
{"type":"function","name":"fixedArrBytes","constant":true,"inputs":[{"name":"str","type":"bytes"},{"name":"fixedArr","type":"uint256[2]"}]},
{"type":"function","name":"mixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"}]},
{"type":"function","name":"doubleFixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"fixedArr2","type":"uint256[3]"}]},
{"type":"function","name":"multipleMixedArrStr","constant":true,"inputs":[{"name":"str","type":"string"},{"name":"fixedArr1","type":"uint256[2]"},{"name":"dynArr","type":"uint256[]"},{"name":"fixedArr2","type":"uint256[3]"}]},
{"type":"function","name":"balance","constant":true},
{"type":"function","name":"send","constant":false,"inputs":[{"name":"amount","type":"uint256"}]},
{"type":"function","name":"test","constant":false,"inputs":[{"name":"number","type":"uint32"}]},
{"type":"function","name":"string","constant":false,"inputs":[{"name":"inputs","type":"string"}]},
{"type":"function","name":"bool","constant":false,"inputs":[{"name":"inputs","type":"bool"}]},
{"type":"function","name":"address","constant":false,"inputs":[{"name":"inputs","type":"address"}]},
{"type":"function","name":"uint64[2]","constant":false,"inputs":[{"name":"inputs","type":"uint64[2]"}]},
{"type":"function","name":"uint64[]","constant":false,"inputs":[{"name":"inputs","type":"uint64[]"}]},
{"type":"function","name":"foo","constant":false,"inputs":[{"name":"inputs","type":"uint32"}]},
{"type":"function","name":"bar","constant":false,"inputs":[{"name":"inputs","type":"uint32"},{"name":"string","type":"uint16"}]},
{"type":"function","name":"_slice","constant":false,"inputs":[{"name":"inputs","type":"uint32[2]"}]},
{"type":"function","name":"__slice256","constant":false,"inputs":[{"name":"inputs","type":"uint256[2]"}]},
{"type":"function","name":"sliceAddress","constant":false,"inputs":[{"name":"inputs","type":"address[]"}]},
{"type":"function","name":"sliceMultiAddress","constant":false,"inputs":[{"name":"a","type":"address[]"},{"name":"b","type":"address[]"}]}
]
`
abi, err := JSON(strings.NewReader(abiJSON))
if err != nil {
t.Fatal(err)
}
for name, m := range abi.Methods {
a := fmt.Sprintf("%v", m)
b := fmt.Sprintf("%v", abi.MethodById(m.Id()))
if a != b {
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
}
}
}

View file

@ -19,6 +19,8 @@ package abi
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// Argument holds the name of the argument and the corresponding type.
@ -29,7 +31,10 @@ type Argument struct {
Indexed bool // indexed is only used by events
}
func (a *Argument) UnmarshalJSON(data []byte) error {
type Arguments []Argument
// UnmarshalJSON implements json.Unmarshaler interface
func (argument *Argument) UnmarshalJSON(data []byte) error {
var extarg struct {
Name string
Type string
@ -40,12 +45,180 @@ func (a *Argument) UnmarshalJSON(data []byte) error {
return fmt.Errorf("argument json err: %v", err)
}
a.Type, err = NewType(extarg.Type)
argument.Type, err = NewType(extarg.Type)
if err != nil {
return err
}
a.Name = extarg.Name
a.Indexed = extarg.Indexed
argument.Name = extarg.Name
argument.Indexed = extarg.Indexed
return nil
}
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
func (arguments Arguments) LengthNonIndexed() int {
out := 0
for _, arg := range arguments {
if !arg.Indexed {
out++
}
}
return out
}
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
func (arguments Arguments) isTuple() bool {
return len(arguments) > 1
}
// Unpack performs the operation hexdata -> Go format
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
if arguments.isTuple() {
return arguments.unpackTuple(v, data)
}
return arguments.unpackAtomic(v, data)
}
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
// make sure the passed value is arguments pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
var (
value = valueOf.Elem()
typ = value.Type()
kind = value.Kind()
)
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err
}
// `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
}

View file

@ -89,7 +89,7 @@ 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.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))
}
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx)
}
@ -306,7 +306,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
b.mu.Lock()
defer b.mu.Unlock()
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx)
}

View file

@ -129,7 +129,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
return string(code), nil
}
// For all others just return as is for now
return string(buffer.Bytes()), nil
return buffer.String(), nil
}
// bindType is a set of type binders that convert Solidity types to some supported

View file

@ -18,7 +18,6 @@ package abi
import (
"fmt"
"reflect"
"strings"
"github.com/ethereum/go-ethereum/common"
@ -31,7 +30,7 @@ import (
type Event struct {
Name string
Anonymous bool
Inputs []Argument
Inputs Arguments
}
// 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, ",")))))
}
// 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
}

View file

@ -17,13 +17,53 @@
package abi
import (
"bytes"
"encoding/hex"
"encoding/json"
"math/big"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var jsonEventTransfer = []byte(`{
"anonymous": false,
"inputs": [
{
"indexed": true, "name": "from", "type": "address"
}, {
"indexed": true, "name": "to", "type": "address"
}, {
"indexed": false, "name": "value", "type": "uint256"
}],
"name": "Transfer",
"type": "event"
}`)
var jsonEventPledge = []byte(`{
"anonymous": false,
"inputs": [{
"indexed": false, "name": "who", "type": "address"
}, {
"indexed": false, "name": "wad", "type": "uint128"
}, {
"indexed": false, "name": "currency", "type": "bytes3"
}],
"name": "Pledge",
"type": "event"
}`)
// 1000000
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
func TestEventId(t *testing.T) {
var table = []struct {
definition string
@ -54,3 +94,223 @@ func TestEventId(t *testing.T) {
}
}
}
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
type testStruct struct {
Value1 [2]uint8
Value2 uint8
}
abi, err := JSON(strings.NewReader(definition))
require.NoError(t, err)
var b bytes.Buffer
var i uint8 = 1
for ; i <= 3; i++ {
b.Write(packNum(reflect.ValueOf(i)))
}
var rst testStruct
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
require.Equal(t, [2]uint8{1, 2}, rst.Value1)
require.Equal(t, uint8(3), rst.Value2)
}
func TestEventTupleUnpack(t *testing.T) {
type EventTransfer struct {
Value *big.Int
}
type EventPledge struct {
Who common.Address
Wad *big.Int
Currency [3]byte
}
type BadEventPledge struct {
Who string
Wad int
Currency [3]byte
}
bigint := new(big.Int)
bigintExpected := big.NewInt(1000000)
bigintExpected2 := big.NewInt(2218516807680)
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
var testCases = []struct {
data string
dest interface{}
expected interface{}
jsonLog []byte
error string
name string
}{{
transferData1,
&EventTransfer{},
&EventTransfer{Value: bigintExpected},
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into structure",
}, {
transferData1,
&[]interface{}{&bigint},
&[]interface{}{&bigintExpected},
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into slice",
}, {
pledgeData1,
&EventPledge{},
&EventPledge{
addr,
bigintExpected2,
[3]byte{'u', 's', 'd'}},
jsonEventPledge,
"",
"Can unpack Pledge event into structure",
}, {
pledgeData1,
&[]interface{}{&common.Address{}, &bigint, &[3]byte{}},
&[]interface{}{
&addr,
&bigintExpected2,
&[3]byte{'u', 's', 'd'}},
jsonEventPledge,
"",
"Can unpack Pledge event into slice",
}, {
pledgeData1,
&[3]interface{}{&common.Address{}, &bigint, &[3]byte{}},
&[3]interface{}{
&addr,
&bigintExpected2,
&[3]byte{'u', 's', 'd'}},
jsonEventPledge,
"",
"Can unpack Pledge event into an array",
}, {
pledgeData1,
&[]interface{}{new(int), 0, 0},
&[]interface{}{},
jsonEventPledge,
"abi: cannot unmarshal common.Address in to int",
"Can not unpack Pledge event into slice with wrong types",
}, {
pledgeData1,
&BadEventPledge{},
&BadEventPledge{},
jsonEventPledge,
"abi: cannot unmarshal common.Address in to string",
"Can not unpack Pledge event into struct with wrong filed types",
}, {
pledgeData1,
&[]interface{}{common.Address{}, new(big.Int)},
&[]interface{}{},
jsonEventPledge,
"abi: insufficient number of elements in the list/array for unpack, want 3, got 2",
"Can not unpack Pledge event into too short slice",
}, {
pledgeData1,
new(map[string]interface{}),
&[]interface{}{},
jsonEventPledge,
"abi: cannot unmarshal tuple into map[string]interface {}",
"Can not unpack Pledge event into map",
}}
for _, tc := range testCases {
assert := assert.New(t)
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
if tc.error == "" {
assert.Nil(err, "Should be able to unpack event data.")
assert.Equal(tc.expected, tc.dest, tc.name)
} else {
assert.EqualError(err, tc.error)
}
})
}
}
func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error {
data, err := hex.DecodeString(hexData)
assert.NoError(err, "Hex data should be a correct hex-string")
var e Event
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
a := ABI{Events: map[string]Event{"e": e}}
return a.Unpack(dest, "e", data)
}
/*
Taken from
https://github.com/ethereum/go-ethereum/pull/15568
*/
type testResult struct {
Values [2]*big.Int
Value1 *big.Int
Value2 *big.Int
}
type testCase struct {
definition string
want testResult
}
func (tc testCase) encoded(intType, arrayType Type) []byte {
var b bytes.Buffer
if tc.want.Value1 != nil {
val, _ := intType.pack(reflect.ValueOf(tc.want.Value1))
b.Write(val)
}
if !reflect.DeepEqual(tc.want.Values, [2]*big.Int{nil, nil}) {
val, _ := arrayType.pack(reflect.ValueOf(tc.want.Values))
b.Write(val)
}
if tc.want.Value2 != nil {
val, _ := intType.pack(reflect.ValueOf(tc.want.Value2))
b.Write(val)
}
return b.Bytes()
}
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
func TestEventUnpackIndexed(t *testing.T) {
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
type testStruct struct {
Value1 uint8
Value2 uint8
}
abi, err := JSON(strings.NewReader(definition))
require.NoError(t, err)
var b bytes.Buffer
b.Write(packNum(reflect.ValueOf(uint8(8))))
var rst testStruct
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
require.Equal(t, uint8(0), rst.Value1)
require.Equal(t, uint8(8), rst.Value2)
}
// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input.
func TestEventIndexedWithArrayUnpack(t *testing.T) {
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
type testStruct struct {
Value1 [2]uint8
Value2 string
}
abi, err := JSON(strings.NewReader(definition))
require.NoError(t, err)
var b bytes.Buffer
stringOut := "abc"
// number of fields that will be encoded * 32
b.Write(packNum(reflect.ValueOf(32)))
b.Write(packNum(reflect.ValueOf(len(stringOut))))
b.Write(common.RightPadBytes([]byte(stringOut), 32))
var rst testStruct
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
require.Equal(t, stringOut, rst.Value2)
}

View file

@ -18,13 +18,12 @@ package abi
import (
"fmt"
"reflect"
"strings"
"github.com/ethereum/go-ethereum/crypto"
)
// Callable method given a `Name` and whether the method is a constant.
// Method represents a callable given a `Name` and whether the method is a constant.
// If the method is `Const` no transaction needs to be created for this
// particular Method call. It can easily be simulated using a local VM.
// For example a `Balance()` method only needs to retrieve something
@ -35,125 +34,8 @@ import (
type Method struct {
Name string
Const bool
Inputs []Argument
Outputs []Argument
}
func (method Method) pack(args ...interface{}) ([]byte, error) {
// Make sure arguments match up and pack them
if len(args) != len(method.Inputs) {
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
}
// variable input is the output appended at the end of packed
// output. This is used for strings and bytes types input.
var variableInput []byte
var ret []byte
for i, a := range args {
input := method.Inputs[i]
// pack the input
packed, err := input.Type.pack(reflect.ValueOf(a))
if err != nil {
return nil, fmt.Errorf("`%s` %v", method.Name, err)
}
// check for a slice type (string, bytes, slice)
if input.Type.requiresLengthPrefix() {
// calculate the offset
offset := len(method.Inputs)*32 + len(variableInput)
// set the offset
ret = append(ret, packNum(reflect.ValueOf(offset))...)
// Append the packed output to the variable input. The variable input
// will be appended at the end of the input.
variableInput = append(variableInput, packed...)
} else {
// append the packed value to the input
ret = append(ret, packed...)
}
}
// append the variable input at the end of the packed input
ret = append(ret, variableInput...)
return ret, nil
}
// unpacks a method return tuple into a struct of corresponding go types
//
// Unpacking can be done into a struct or a slice/array.
func (method Method) tupleUnpack(v interface{}, output []byte) error {
// make sure the passed value is a pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
var (
value = valueOf.Elem()
typ = value.Type()
)
j := 0
for i := 0; i < len(method.Outputs); i++ {
toUnpack := method.Outputs[i]
if toUnpack.Type.T == ArrayTy {
// need to move this up because they read sequentially
j += toUnpack.Type.Size
}
marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output)
if err != nil {
return err
}
reflectValue := reflect.ValueOf(marshalledValue)
switch value.Kind() {
case reflect.Struct:
for j := 0; j < typ.NumField(); j++ {
field := typ.Field(j)
// TODO read tags: `abi:"fieldName"`
if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
return err
}
}
}
case reflect.Slice, reflect.Array:
if value.Len() < i {
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(method.Outputs), value.Len())
}
v := value.Index(i)
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
}
reflectValue := reflect.ValueOf(marshalledValue)
if err := set(v.Elem(), reflectValue, method.Outputs[i]); err != nil {
return err
}
default:
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
}
}
return nil
}
func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 }
func (method Method) singleUnpack(v interface{}, output []byte) error {
// make sure the passed value is a pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
value := valueOf.Elem()
marshalledValue, err := toGoType(0, method.Outputs[0].Type, output)
if err != nil {
return err
}
if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
return err
}
return nil
Inputs Arguments
Outputs Arguments
}
// Sig returns the methods string signature according to the ABI spec.
@ -163,35 +45,35 @@ func (method Method) singleUnpack(v interface{}, output []byte) error {
// function foo(uint32 a, int b) = "foo(uint32,int256)"
//
// Please note that "int" is substitute for its canonical representation "int256"
func (m Method) Sig() string {
types := make([]string, len(m.Inputs))
func (method Method) Sig() string {
types := make([]string, len(method.Inputs))
i := 0
for _, input := range m.Inputs {
for _, input := range method.Inputs {
types[i] = input.Type.String()
i++
}
return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ","))
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
}
func (m Method) String() string {
inputs := make([]string, len(m.Inputs))
for i, input := range m.Inputs {
func (method Method) String() string {
inputs := make([]string, len(method.Inputs))
for i, input := range method.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
}
outputs := make([]string, len(m.Outputs))
for i, output := range m.Outputs {
outputs := make([]string, len(method.Outputs))
for i, output := range method.Outputs {
if len(output.Name) > 0 {
outputs[i] = fmt.Sprintf("%v ", output.Name)
}
outputs[i] += output.Type.String()
}
constant := ""
if m.Const {
if method.Const {
constant = "constant "
}
return fmt.Sprintf("function %v(%v) %sreturns(%v)", m.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
}
func (m Method) Id() []byte {
return crypto.Keccak256([]byte(m.Sig()))[:4]
func (method Method) Id() []byte {
return crypto.Keccak256([]byte(method.Sig()))[:4]
}

View file

@ -48,9 +48,8 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
case BoolTy:
if reflectValue.Bool() {
return math.PaddedBigBytes(common.Big1, 32)
} else {
return math.PaddedBigBytes(common.Big0, 32)
}
return math.PaddedBigBytes(common.Big0, 32)
case BytesTy:
if reflectValue.Kind() == reflect.Array {
reflectValue = mustArrayToByteSlice(reflectValue)

View file

@ -85,3 +85,28 @@ func set(dst, src reflect.Value, output Argument) error {
}
return nil
}
// requireAssignable assures that `dest` is a pointer and it's not an interface.
func requireAssignable(dst, src reflect.Value) error {
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type())
}
return nil
}
// requireUnpackKind verifies preconditions for unpacking `args` into `kind`
func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
args Arguments) error {
switch k {
case reflect.Struct:
case reflect.Slice, reflect.Array:
if minLen := args.LengthNonIndexed(); v.Len() < minLen {
return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d",
minLen, v.Len())
}
default:
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t)
}
return nil
}

View file

@ -24,6 +24,7 @@ import (
"strings"
)
// Type enumerator
const (
IntTy byte = iota
UintTy
@ -100,7 +101,7 @@ func NewType(t string) (typ Type, err error) {
return Type{}, fmt.Errorf("invalid formatting of array type")
}
return typ, err
} else {
}
// parse the type and size of the abi-type.
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
// varSize is the size of the variable
@ -119,9 +120,7 @@ func NewType(t string) (typ Type, err error) {
}
}
// varType is the parsed abi type
varType := parsedType[1]
switch varType {
switch varType := parsedType[1]; varType {
case "int":
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
typ.Size = varSize
@ -162,7 +161,6 @@ func NewType(t string) (typ Type, err error) {
default:
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
}
}
return
}

View file

@ -25,15 +25,6 @@ import (
"github.com/ethereum/go-ethereum/common"
)
// unpacker is a utility interface that enables us to have
// abstraction between events and methods and also to properly
// "unpack" them; e.g. events use Inputs, methods use Outputs.
type unpacker interface {
tupleUnpack(v interface{}, output []byte) error
singleUnpack(v interface{}, output []byte) error
isTupleReturn() bool
}
// reads the integer based on its kind
func readInteger(kind reflect.Kind, b []byte) interface{} {
switch kind {
@ -79,7 +70,7 @@ func readBool(word []byte) (bool, error) {
// This enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes)
func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
if t.T != FunctionTy {
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array.")
return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array")
}
if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 {
err = fmt.Errorf("abi: got improperly encoded function type, got %v", word)
@ -92,7 +83,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
// through reflection, creates a fixed array to be read from
func readFixedBytes(t Type, word []byte) (interface{}, error) {
if t.T != FixedBytesTy {
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array.")
return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array")
}
// convert
array := reflect.New(t.Type).Elem()
@ -203,14 +194,3 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
//fmt.Printf("LENGTH PREFIX INFO: \nsize: %v\noffset: %v\nstart: %v\n", length, offset, start)
return
}
// checks for proper formatting of byte output
func bytesAreProper(output []byte) error {
if len(output) == 0 {
return fmt.Errorf("abi: unmarshalling empty output")
} else if len(output)%32 != 0 {
return fmt.Errorf("abi: improperly formatted output")
} else {
return nil
}
}

View file

@ -22,10 +22,12 @@ import (
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
type unpackTest struct {
@ -261,6 +263,7 @@ var unpackTests = []unpackTest{
func TestUnpack(t *testing.T) {
for i, test := range unpackTests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def)
abi, err := JSON(strings.NewReader(def))
if err != nil {
@ -274,65 +277,116 @@ func TestUnpack(t *testing.T) {
err = abi.Unpack(outptr.Interface(), "method", encb)
if err := test.checkError(err); err != nil {
t.Errorf("test %d (%v) failed: %v", i, test.def, err)
continue
return
}
out := outptr.Elem().Interface()
if !reflect.DeepEqual(test.want, out) {
t.Errorf("test %d (%v) failed: expected %v, got %v", i, test.def, test.want, out)
}
})
}
}
func TestMultiReturnWithStruct(t *testing.T) {
type methodMultiOutput struct {
Int *big.Int
String string
}
func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) {
const definition = `[
{ "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]`
var expected = methodMultiOutput{big.NewInt(1), "hello"}
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
require.NoError(err)
// using buff to make the code readable
buff := new(bytes.Buffer)
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001"))
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040"))
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000005"))
stringOut := "hello"
buff.Write(common.RightPadBytes([]byte(stringOut), 32))
buff.Write(common.RightPadBytes([]byte(expected.String), 32))
return abi, buff.Bytes(), expected
}
var inter struct {
Int *big.Int
String string
}
err = abi.Unpack(&inter, "multi", buff.Bytes())
if err != nil {
t.Error(err)
}
if inter.Int == nil || inter.Int.Cmp(big.NewInt(1)) != 0 {
t.Error("expected Int to be 1 got", inter.Int)
}
if inter.String != stringOut {
t.Error("expected String to be", stringOut, "got", inter.String)
}
var reversed struct {
func TestMethodMultiReturn(t *testing.T) {
type reversed struct {
String string
Int *big.Int
}
err = abi.Unpack(&reversed, "multi", buff.Bytes())
abi, data, expected := methodMultiReturn(require.New(t))
bigint := new(big.Int)
var testCases = []struct {
dest interface{}
expected interface{}
error string
name string
}{{
&methodMultiOutput{},
&expected,
"",
"Can unpack into structure",
}, {
&reversed{},
&reversed{expected.String, expected.Int},
"",
"Can unpack into reversed structure",
}, {
&[]interface{}{&bigint, new(string)},
&[]interface{}{&expected.Int, &expected.String},
"",
"Can unpack into a slice",
}, {
&[2]interface{}{&bigint, new(string)},
&[2]interface{}{&expected.Int, &expected.String},
"",
"Can unpack into an array",
}, {
&[]interface{}{new(int), new(int)},
&[]interface{}{&expected.Int, &expected.String},
"abi: cannot unmarshal *big.Int in to int",
"Can not unpack into a slice with wrong types",
}, {
&[]interface{}{new(int)},
&[]interface{}{},
"abi: insufficient number of elements in the list/array for unpack, want 2, got 1",
"Can not unpack into a slice with wrong types",
}}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
err := abi.Unpack(tc.dest, "multi", data)
if tc.error == "" {
require.Nil(err, "Should be able to unpack method outputs.")
require.Equal(tc.expected, tc.dest)
} else {
require.EqualError(err, tc.error)
}
})
}
}
func TestMultiReturnWithArray(t *testing.T) {
const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Error(err)
t.Fatal(err)
}
buff := new(bytes.Buffer)
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000009"))
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008"))
if reversed.Int == nil || reversed.Int.Cmp(big.NewInt(1)) != 0 {
t.Error("expected Int to be 1 got", reversed.Int)
ret1, ret1Exp := new([3]uint64), [3]uint64{9, 9, 9}
ret2, ret2Exp := new(uint64), uint64(8)
if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil {
t.Fatal(err)
}
if reversed.String != stringOut {
t.Error("expected String to be", stringOut, "got", reversed.String)
if !reflect.DeepEqual(*ret1, ret1Exp) {
t.Error("array result", *ret1, "!= Expected", ret1Exp)
}
if *ret2 != ret2Exp {
t.Error("int result", *ret2, "!= Expected", ret2Exp)
}
}
@ -368,11 +422,11 @@ func TestUnmarshal(t *testing.T) {
if err != nil {
t.Error(err)
} else {
if bytes.Compare(p0, p0Exp) != 0 {
if !bytes.Equal(p0, p0Exp) {
t.Errorf("unexpected value unpacked: want %x, got %x", p0Exp, p0)
}
if bytes.Compare(p1[:], p1Exp) != 0 {
if !bytes.Equal(p1[:], p1Exp) {
t.Errorf("unexpected value unpacked: want %x, got %x", p1Exp, p1)
}
}

View file

@ -58,6 +58,9 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
if err != nil {
return nil, errors.New("invalid hex in encSeed")
}
if len(encSeedBytes) < 16 {
return nil, errors.New("invalid encSeed, too short")
}
iv := encSeedBytes[:16]
cipherText := encSeedBytes[16:]
/*

View file

@ -23,8 +23,8 @@ environment:
install:
- git submodule update --init
- rmdir C:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-%GETH_ARCH%.zip
- 7z x go1.9.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.2.windows-%GETH_ARCH%.zip
- 7z x go1.9.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
- go version
- gcc --version

View file

@ -260,8 +260,7 @@ func NewTree(hasher BaseHasher, segmentSize, segmentCount int) *Tree {
for d := 1; d <= depth(segmentCount); d++ {
nodes := make([]*Node, count)
for i := 0; i < len(nodes); i++ {
var parent *Node
parent = prevlevel[i/2]
parent := prevlevel[i/2]
t := NewNode(level, i, parent)
nodes[i] = t
}

View file

@ -319,17 +319,25 @@ func doLint(cmdline []string) {
packages = flag.CommandLine.Args()
}
// Get metalinter and install all supported linters
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v1"))
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), "--install")
build.MustRun(goTool("get", "gopkg.in/alecthomas/gometalinter.v2"))
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), "--install")
// Run fast linters batched together
configs := []string{"--vendor", "--disable-all", "--enable=vet", "--enable=gofmt", "--enable=misspell"}
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
configs := []string{
"--vendor",
"--disable-all",
"--enable=vet",
"--enable=gofmt",
"--enable=misspell",
"--enable=goconst",
"--min-occurrences=6", // for goconst
}
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
// Run slow linters one by one
for _, linter := range []string{"unconvert"} {
for _, linter := range []string{"unconvert", "gosimple"} {
configs = []string{"--vendor", "--deadline=10m", "--disable-all", "--enable=" + linter}
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v1"), append(configs, packages...)...)
build.MustRunCommand(filepath.Join(GOBIN, "gometalinter.v2"), append(configs, packages...)...)
}
}

41
cmd/ethkey/README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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))
}

View file

@ -19,6 +19,7 @@ package main
import (
"encoding/json"
"io"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
@ -35,6 +36,10 @@ func NewJSONLogger(cfg *vm.LogConfig, writer io.Writer) *JSONLogger {
return &JSONLogger{json.NewEncoder(writer), cfg}
}
func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
return nil
}
// CaptureState outputs state information on the logger.
func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
log := vm.StructLog{
@ -56,6 +61,11 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
return l.encoder.Encode(log)
}
// CaptureFault outputs state information on the logger.
func (l *JSONLogger) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, memory *vm.Memory, stack *vm.Stack, contract *vm.Contract, depth int, err error) error {
return nil
}
// CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
type endLog struct {

View file

@ -21,7 +21,6 @@ package main
import (
"bytes"
"compress/zlib"
"context"
"encoding/json"
"errors"
@ -506,7 +505,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
// Send an error if too frequent funding, othewise a success
if !fund {
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil {
if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil { // nolint: gosimple
log.Warn("Failed to send funding error to client", "err", err)
return
}
@ -698,11 +697,7 @@ func authTwitter(url string) (string, string, common.Address, error) {
}
defer res.Body.Close()
reader, err := zlib.NewReader(res.Body)
if err != nil {
return "", "", common.Address{}, err
}
body, err := ioutil.ReadAll(reader)
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", common.Address{}, err
}

View file

@ -20,6 +20,7 @@ import (
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils"
@ -114,8 +115,19 @@ func localConsole(ctx *cli.Context) error {
func remoteConsole(ctx *cli.Context) error {
// Attach to a remotely running geth instance and start the JavaScript console
endpoint := ctx.Args().First()
if endpoint == "" && ctx.GlobalIsSet(utils.DataDirFlag.Name) {
endpoint = fmt.Sprintf("%s/geth.ipc", ctx.GlobalString(utils.DataDirFlag.Name))
if endpoint == "" {
path := node.DefaultDataDir()
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
path = ctx.GlobalString(utils.DataDirFlag.Name)
}
if path != "" {
if ctx.GlobalBool(utils.TestnetFlag.Name) {
path = filepath.Join(path, "testnet")
} else if ctx.GlobalBool(utils.RinkebyFlag.Name) {
path = filepath.Join(path, "rinkeby")
}
}
endpoint = fmt.Sprintf("%s/geth.ipc", path)
}
client, err := dialRPC(endpoint)
if err != nil {

File diff suppressed because one or more lines are too long

View file

@ -39,6 +39,8 @@ ADD genesis.json /genesis.json
ADD account.json /account.json
ADD account.pass /account.pass
EXPOSE 8080 30303 30303/udp
ENTRYPOINT [ \
"faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \

321
cmd/swarm/config.go Normal file
View 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
View 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()
}

View file

@ -48,6 +48,7 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm"
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
"gopkg.in/urfave/cli.v1"
)
@ -68,47 +69,56 @@ var (
ChequebookAddrFlag = cli.StringFlag{
Name: "chequebook",
Usage: "chequebook contract address",
EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
}
SwarmAccountFlag = cli.StringFlag{
Name: "bzzaccount",
Usage: "Swarm account key file",
EnvVar: SWARM_ENV_ACCOUNT,
}
SwarmListenAddrFlag = cli.StringFlag{
Name: "httpaddr",
Usage: "Swarm HTTP API listening interface",
EnvVar: SWARM_ENV_LISTEN_ADDR,
}
SwarmPortFlag = cli.StringFlag{
Name: "bzzport",
Usage: "Swarm local http api port",
EnvVar: SWARM_ENV_PORT,
}
SwarmNetworkIdFlag = cli.IntFlag{
Name: "bzznetworkid",
Usage: "Network identifier (integer, default 3=swarm testnet)",
EnvVar: SWARM_ENV_NETWORK_ID,
}
SwarmConfigPathFlag = cli.StringFlag{
Name: "bzzconfig",
Usage: "Swarm config file path (datadir/bzz)",
Usage: "DEPRECATED: please use --config path/to/TOML-file",
}
SwarmSwapEnabledFlag = cli.BoolFlag{
Name: "swap",
Usage: "Swarm SWAP enabled (default false)",
EnvVar: SWARM_ENV_SWAP_ENABLE,
}
SwarmSwapAPIFlag = cli.StringFlag{
Name: "swap-api",
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
EnvVar: SWARM_ENV_SWAP_API,
}
SwarmSyncEnabledFlag = cli.BoolTFlag{
Name: "sync",
Usage: "Swarm Syncing enabled (default true)",
EnvVar: SWARM_ENV_SYNC_ENABLE,
}
EnsAPIFlag = cli.StringFlag{
Name: "ens-api",
Usage: "URL of the Ethereum API provider to use for ENS record lookups",
Value: node.DefaultIPCEndpoint("geth"),
EnvVar: SWARM_ENV_ENS_API,
}
EnsAddrFlag = cli.StringFlag{
Name: "ens-addr",
Usage: "ENS contract address (default is detected as testnet or mainnet using --ens-api)",
EnvVar: SWARM_ENV_ENS_ADDR,
}
SwarmApiFlag = cli.StringFlag{
Name: "bzzapi",
@ -138,6 +148,7 @@ var (
CorsStringFlag = cli.StringFlag{
Name: "corsdomain",
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
EnvVar: SWARM_ENV_CORS,
}
// the following flags are deprecated and should be removed in the future
@ -147,6 +158,12 @@ var (
}
)
//declare a few constant error messages, useful for later error check comparisons in test
var (
SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables"
SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set"
)
var defaultNodeConfig = node.DefaultConfig
// This init function sets defaults so cmd/swarm can run alongside geth.
@ -302,6 +319,8 @@ Remove corrupt entries from a local chunk database.
DEPRECATED: use 'swarm db clean'.
`,
},
// See config.go
DumpConfigCommand,
}
sort.Sort(cli.CommandsByName(app.Commands))
@ -325,6 +344,7 @@ DEPRECATED: use 'swarm db clean'.
CorsStringFlag,
EnsAPIFlag,
EnsAddrFlag,
SwarmTomlConfigPathFlag,
SwarmConfigPathFlag,
SwarmSwapEnabledFlag,
SwarmSwapAPIFlag,
@ -377,19 +397,32 @@ func version(ctx *cli.Context) error {
}
func bzzd(ctx *cli.Context) error {
// exit if the deprecated --ethapi flag is set
if ctx.GlobalString(DeprecatedEthAPIFlag.Name) != "" {
utils.Fatalf("--ethapi is no longer a valid command line flag, please use --ens-api and/or --swap-api.")
//build a valid bzzapi.Config from all available sources:
//default config, file config, command line and env vars
bzzconfig, err := buildConfig(ctx)
if err != nil {
utils.Fatalf("unable to configure swarm: %v", err)
}
cfg := defaultNodeConfig
//geth only supports --datadir via command line
//in order to be consistent within swarm, if we pass --datadir via environment variable
//or via config file, we get the same directory for geth and swarm
if _, err := os.Stat(bzzconfig.Path); err == nil {
cfg.DataDir = bzzconfig.Path
}
//setup the ethereum node
utils.SetNodeConfig(ctx, &cfg)
stack, err := node.New(&cfg)
if err != nil {
utils.Fatalf("can't create node: %v", err)
}
registerBzzService(ctx, stack)
//a few steps need to be done after the config phase is completed,
//due to overriding behavior
initSwarmNode(bzzconfig, stack, ctx)
//register BZZ as node.Service in the ethereum node
registerBzzService(bzzconfig, ctx, stack)
//start the node
utils.StartNode(stack)
go func() {
@ -401,13 +434,12 @@ func bzzd(ctx *cli.Context) error {
stack.Stop()
}()
networkId := ctx.GlobalUint64(SwarmNetworkIdFlag.Name)
// Add bootnodes as initial peers.
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
bootnodes := strings.Split(ctx.GlobalString(utils.BootnodesFlag.Name), ",")
if bzzconfig.BootNodes != "" {
bootnodes := strings.Split(bzzconfig.BootNodes, ",")
injectBootnodes(stack.Server(), bootnodes)
} else {
if networkId == 3 {
if bzzconfig.NetworkId == 3 {
injectBootnodes(stack.Server(), testbetBootNodes)
}
}
@ -448,61 +480,31 @@ func detectEnsAddr(client *rpc.Client) (common.Address, error) {
}
}
func registerBzzService(ctx *cli.Context, stack *node.Node) {
prvkey := getAccount(ctx, stack)
chbookaddr := common.HexToAddress(ctx.GlobalString(ChequebookAddrFlag.Name))
bzzdir := ctx.GlobalString(SwarmConfigPathFlag.Name)
if bzzdir == "" {
bzzdir = stack.InstanceDir()
}
bzzconfig, err := bzzapi.NewConfig(bzzdir, chbookaddr, prvkey, ctx.GlobalUint64(SwarmNetworkIdFlag.Name))
if err != nil {
utils.Fatalf("unable to configure swarm: %v", err)
}
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
if len(bzzport) > 0 {
bzzconfig.Port = bzzport
}
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
bzzconfig.ListenAddr = bzzaddr
}
swapEnabled := ctx.GlobalBool(SwarmSwapEnabledFlag.Name)
syncEnabled := ctx.GlobalBoolT(SwarmSyncEnabledFlag.Name)
swapapi := ctx.GlobalString(SwarmSwapAPIFlag.Name)
if swapEnabled && swapapi == "" {
utils.Fatalf("SWAP is enabled but --swap-api is not set")
}
ensapi := ctx.GlobalString(EnsAPIFlag.Name)
ensAddr := ctx.GlobalString(EnsAddrFlag.Name)
cors := ctx.GlobalString(CorsStringFlag.Name)
func registerBzzService(bzzconfig *bzzapi.Config, ctx *cli.Context, stack *node.Node) {
//define the swarm service boot function
boot := func(ctx *node.ServiceContext) (node.Service, error) {
var swapClient *ethclient.Client
if swapapi != "" {
log.Info("connecting to SWAP API", "url", swapapi)
swapClient, err = ethclient.Dial(swapapi)
var err error
if bzzconfig.SwapApi != "" {
log.Info("connecting to SWAP API", "url", bzzconfig.SwapApi)
swapClient, err = ethclient.Dial(bzzconfig.SwapApi)
if err != nil {
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", swapapi, err)
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", bzzconfig.SwapApi, err)
}
}
var ensClient *ethclient.Client
if ensapi != "" {
log.Info("connecting to ENS API", "url", ensapi)
client, err := rpc.Dial(ensapi)
if bzzconfig.EnsApi != "" {
log.Info("connecting to ENS API", "url", bzzconfig.EnsApi)
client, err := rpc.Dial(bzzconfig.EnsApi)
if err != nil {
return nil, fmt.Errorf("error connecting to ENS API %s: %s", ensapi, err)
return nil, fmt.Errorf("error connecting to ENS API %s: %s", bzzconfig.EnsApi, err)
}
ensClient = ethclient.NewClient(client)
if ensAddr != "" {
bzzconfig.EnsRoot = common.HexToAddress(ensAddr)
} else {
//no ENS root address set yet
if bzzconfig.EnsRoot == (common.Address{}) {
ensAddr, err := detectEnsAddr(client)
if err == nil {
bzzconfig.EnsRoot = ensAddr
@ -512,21 +514,21 @@ func registerBzzService(ctx *cli.Context, stack *node.Node) {
}
}
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, swapEnabled, syncEnabled, cors)
return swarm.NewSwarm(ctx, swapClient, ensClient, bzzconfig, bzzconfig.SwapEnabled, bzzconfig.SyncEnabled, bzzconfig.Cors)
}
//register within the ethereum node
if err := stack.Register(boot); err != nil {
utils.Fatalf("Failed to register the Swarm service: %v", err)
}
}
func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
keyid := ctx.GlobalString(SwarmAccountFlag.Name)
if keyid == "" {
utils.Fatalf("Option %q is required", SwarmAccountFlag.Name)
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
//an account is mandatory
if bzzaccount == "" {
utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT)
}
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(keyid); err == nil {
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
return key
}
@ -534,7 +536,7 @@ func getAccount(ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
am := stack.AccountManager()
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
return decryptStoreAccount(ks, keyid, utils.MakePasswordList(ctx))
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
}
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
@ -552,7 +554,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
utils.Fatalf("Can't find swarm account key %s", account)
}
if err != nil {
utils.Fatalf("Can't find swarm account key: %v", err)
utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
}
keyjson, err := ioutil.ReadFile(a.URL.Path)
if err != nil {

View file

@ -30,6 +30,8 @@ import (
"gopkg.in/urfave/cli.v1"
)
const bzzManifestJSON = "application/bzz-manifest+json"
func add(ctx *cli.Context) {
args := ctx.Args()
if len(args) < 3 {
@ -145,7 +147,7 @@ func addEntryToManifest(ctx *cli.Context, mhash, path, hash, ctype string) strin
if path == entry.Path {
utils.Fatalf("Path %s already present, not adding anything", path)
} else {
if entry.ContentType == "application/bzz-manifest+json" {
if entry.ContentType == bzzManifestJSON {
prfxlen := strings.HasPrefix(path, entry.Path)
if prfxlen && len(path) > len(longestPathEntry.Path) {
longestPathEntry = entry
@ -207,7 +209,7 @@ func updateEntryInManifest(ctx *cli.Context, mhash, path, hash, ctype string) st
if path == entry.Path {
newEntry = entry
} else {
if entry.ContentType == "application/bzz-manifest+json" {
if entry.ContentType == bzzManifestJSON {
prfxlen := strings.HasPrefix(path, entry.Path)
if prfxlen && len(path) > len(longestPathEntry.Path) {
longestPathEntry = entry
@ -281,7 +283,7 @@ func removeEntryFromManifest(ctx *cli.Context, mhash, path string) string {
if path == entry.Path {
entryToRemove = entry
} else {
if entry.ContentType == "application/bzz-manifest+json" {
if entry.ContentType == bzzManifestJSON {
prfxlen := strings.HasPrefix(path, entry.Path)
if prfxlen && len(path) > len(longestPathEntry.Path) {
longestPathEntry = entry

View file

@ -27,6 +27,7 @@ import (
"time"
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/internal/cmdtest"
"github.com/ethereum/go-ethereum/node"
@ -156,9 +157,9 @@ type testNode struct {
const testPassphrase = "swarm-test-passphrase"
func newTestNode(t *testing.T, dir string) *testNode {
func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
// create key
conf := &node.Config{
conf = &node.Config{
DataDir: dir,
IPCPath: "bzzd.ipc",
NoUSB: true,
@ -167,18 +168,24 @@ func newTestNode(t *testing.T, dir string) *testNode {
if err != nil {
t.Fatal(err)
}
account, err := n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
if err != nil {
t.Fatal(err)
}
node := &testNode{Dir: dir}
// use a unique IPCPath when running tests on Windows
if runtime.GOOS == "windows" {
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
}
return conf, account
}
func newTestNode(t *testing.T, dir string) *testNode {
conf, account := getTestAccount(t, dir)
node := &testNode{Dir: dir}
// assign ports
httpPort, err := assignTCPPort()
if err != nil {

View file

@ -52,3 +52,13 @@ func getFdLimit() (int, error) {
}
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
}

View file

@ -16,12 +16,22 @@
package utils
import "testing"
import (
"fmt"
"testing"
)
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
// per this process can be retrieved.
func TestFileDescriptorLimits(t *testing.T) {
target := 4096
hardlimit, err := 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 {
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)

View file

@ -48,3 +48,13 @@ func getFdLimit() (int, error) {
}
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
}

View file

@ -39,3 +39,9 @@ func getFdLimit() (int, error) {
// Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
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()
}

View file

@ -746,6 +746,12 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
if err != nil || index < 0 {
return accounts.Account{}, fmt.Errorf("invalid account address or index %q", account)
}
log.Warn("-------------------------------------------------------------------")
log.Warn("Referring to accounts by order in the keystore folder is dangerous!")
log.Warn("This functionality is deprecated and will be removed in the future!")
log.Warn("Please use explicit addresses! (can search via `geth account list`)")
log.Warn("-------------------------------------------------------------------")
accs := ks.Accounts()
if len(accs) <= index {
return accounts.Account{}, fmt.Errorf("index %d higher than number of accounts %d", index, len(accs))
@ -762,15 +768,6 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
}
cfg.Etherbase = account.Address
return
}
accounts := ks.Accounts()
if (cfg.Etherbase == common.Address{}) {
if len(accounts) > 0 {
cfg.Etherbase = accounts[0].Address
} else {
log.Warn("No etherbase set and no accounts found as default")
}
}
}

View file

@ -17,9 +17,7 @@
// Package common contains various helper functions.
package common
import (
"encoding/hex"
)
import "encoding/hex"
func ToHex(b []byte) string {
hex := Bytes2Hex(b)
@ -35,12 +33,11 @@ func FromHex(s string) []byte {
if s[0:2] == "0x" || s[0:2] == "0X" {
s = s[2:]
}
}
if len(s)%2 == 1 {
s = "0" + s
}
return Hex2Bytes(s)
}
return nil
}
// Copy bytes
@ -56,14 +53,24 @@ func CopyBytes(b []byte) (copiedBytes []byte) {
return
}
func HasHexPrefix(str string) bool {
l := len(str)
return l >= 2 && str[0:2] == "0x"
func hasHexPrefix(str string) bool {
return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X')
}
func IsHex(str string) bool {
l := len(str)
return l >= 4 && l%2 == 0 && str[0:2] == "0x"
func isHexCharacter(c byte) bool {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
}
func isHex(str string) bool {
if len(str)%2 != 0 {
return false
}
for _, c := range []byte(str) {
if !isHexCharacter(c) {
return false
}
}
return true
}
func Bytes2Hex(d []byte) string {

View file

@ -34,19 +34,6 @@ func (s *BytesSuite) TestCopyBytes(c *checker.C) {
c.Assert(res1, checker.DeepEquals, exp1)
}
func (s *BytesSuite) TestIsHex(c *checker.C) {
data1 := "a9e67e"
exp1 := false
res1 := IsHex(data1)
c.Assert(res1, checker.DeepEquals, exp1)
data2 := "0xa9e67e00"
exp2 := true
res2 := IsHex(data2)
c.Assert(res2, checker.DeepEquals, exp2)
}
func (s *BytesSuite) TestLeftPadBytes(c *checker.C) {
val1 := []byte{1, 2, 3, 4}
exp1 := []byte{0, 0, 0, 0, 1, 2, 3, 4}
@ -74,7 +61,28 @@ func TestFromHex(t *testing.T) {
expected := []byte{1}
result := FromHex(input)
if !bytes.Equal(expected, result) {
t.Errorf("Expected % x got % x", expected, result)
t.Errorf("Expected %x got %x", expected, result)
}
}
func TestIsHex(t *testing.T) {
tests := []struct {
input string
ok bool
}{
{"", true},
{"0", false},
{"00", true},
{"a9e67e", true},
{"A9E67E", true},
{"0xa9e67e", false},
{"a9e67e001", false},
{"0xHELLO_MY_NAME_IS_STEVEN_@#$^&*", false},
}
for _, test := range tests {
if ok := isHex(test.input); ok != test.ok {
t.Errorf("isHex(%q) = %v, want %v", test.input, ok, test.ok)
}
}
}
@ -83,6 +91,15 @@ func TestFromHexOddLength(t *testing.T) {
expected := []byte{1}
result := FromHex(input)
if !bytes.Equal(expected, result) {
t.Errorf("Expected % x got % x", expected, result)
t.Errorf("Expected %x got %x", expected, result)
}
}
func TestNoPrefixShortHexOddLength(t *testing.T) {
input := "1"
expected := []byte{1}
result := FromHex(input)
if !bytes.Equal(expected, result) {
t.Errorf("Expected %x got %x", expected, result)
}
}

View file

@ -150,13 +150,10 @@ func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
// IsHexAddress verifies whether a string can represent a valid hex-encoded
// Ethereum address or not.
func IsHexAddress(s string) bool {
if len(s) == 2+2*AddressLength && IsHex(s) {
return true
if hasHexPrefix(s) {
s = s[2:]
}
if len(s) == 2*AddressLength && IsHex("0x"+s) {
return true
}
return false
return len(s) == 2*AddressLength && isHex(s)
}
// Get the string representation of the underlying address

View file

@ -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) {
var tests = []struct {
Prefix string

View file

@ -510,7 +510,6 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
header.Nonce = types.BlockNonce{}
number := header.Number.Uint64()
// Assemble the voting snapshot to check which votes make sense
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil {
@ -538,10 +537,8 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
c.lock.RUnlock()
}
// Set the correct difficulty
header.Difficulty = diffNoTurn
if snap.inturn(header.Number.Uint64(), c.signer) {
header.Difficulty = diffInTurn
}
header.Difficulty = CalcDifficulty(snap, c.signer)
// Ensure the extra data has all it's components
if len(header.Extra) < extraVanity {
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
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 {
// It's not our turn explicitly to sign, delay it a bit
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
}
// 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
// controlling the signer voting.
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"math/big"
)
// ChainReader defines a small collection of methods needed to access the local
@ -88,6 +89,10 @@ type Engine interface {
// seal place on top.
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(chain ChainReader) []rpc.API
}

View file

@ -39,6 +39,7 @@ var (
FrontierBlockReward *big.Int = big.NewInt(5e+18) // Block reward in wei for successfully mining a block
ByzantiumBlockReward *big.Int = big.NewInt(3e+18) // Block reward in wei for successfully mining a block upward from Byzantium
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
@ -231,7 +232,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
return errLargeBlockTime
}
} 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
}
}
@ -239,7 +240,8 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
return errZeroBlockTime
}
// 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 {
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
// the difficulty that a new block should have when created at time
// 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 {
next := new(big.Int).Add(parent.Number, big1)
switch {
@ -501,8 +509,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
if parent == nil {
return consensus.ErrUnknownAncestor
}
header.Difficulty = CalcDifficulty(chain.Config(), header.Time.Uint64(), parent)
header.Difficulty = ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)
return nil
}
@ -510,7 +517,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
// 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) {
// 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 seems complete, assemble into a block and return
@ -526,8 +533,7 @@ var (
// AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// 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
blockReward := FrontierBlockReward
if config.IsByzantium(header.Number) {

View file

@ -71,6 +71,7 @@ func TestCalcDifficulty(t *testing.T) {
}
config := &params.ChainConfig{HomesteadBlock: big.NewInt(1150000)}
for name, test := range tests {
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{

View file

@ -92,6 +92,9 @@ func New(config Config) (*Console, error) {
printer: config.Printer,
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 {
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
obj.Set("sleepBlocks", bridge.SleepBlocks)
obj.Set("sleep", bridge.Sleep)
obj.Set("clearHistory", c.clearHistory)
}
// Preload any JavaScript files before starting the console
for _, path := range preload {
@ -216,6 +220,16 @@ func (c *Console) init(preload []string) error {
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
// stream the output into the configured output stream instead of stdout.
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)
}
// 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 {
if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
return err

View file

@ -68,6 +68,7 @@ func (p *hookedPrompter) PromptConfirm(prompt string) (bool, error) {
}
func (p *hookedPrompter) SetHistory(history []string) {}
func (p *hookedPrompter) AppendHistory(command string) {}
func (p *hookedPrompter) ClearHistory() {}
func (p *hookedPrompter) SetWordCompleter(completer WordCompleter) {}
// 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()
output := string(tester.output.Bytes())
output := tester.output.String()
if want := "Welcome"; !strings.Contains(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)
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")
}
}
@ -217,7 +218,7 @@ func TestInteractive(t *testing.T) {
case <-time.After(time.Second):
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")
}
}
@ -229,7 +230,7 @@ func TestPreload(t *testing.T) {
defer tester.Close(t)
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")
}
}
@ -242,7 +243,7 @@ func TestExecute(t *testing.T) {
tester.console.Execute("exec.js")
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")
}
}
@ -274,7 +275,7 @@ func TestPrettyPrint(t *testing.T) {
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)
}
}
@ -286,7 +287,7 @@ func TestPrettyError(t *testing.T) {
tester.console.Evaluate("throw 'hello'")
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)
}
}

View file

@ -51,6 +51,9 @@ type UserPrompter interface {
// if and only if the prompt to append was a valid command.
AppendHistory(command string)
// ClearHistory clears the entire history
ClearHistory()
// SetWordCompleter sets the completion function that the prompter will call to
// fetch completion candidates when the user presses tab.
SetWordCompleter(completer WordCompleter)
@ -158,6 +161,11 @@ func (p *terminalPrompter) AppendHistory(command string) {
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
// fetch completion candidates when the user presses tab.
func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) {

View file

@ -137,7 +137,7 @@ func (r *ReleaseService) checkVersion() {
if err != nil {
if err == bind.ErrNoCode {
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)
}
return

View file

@ -114,10 +114,7 @@ func PrintDisassembled(code string) error {
fmt.Printf("%06v: %v\n", it.PC(), it.Op())
}
}
if err := it.Error(); err != nil {
return err
}
return nil
return it.Error()
}
// Return all disassembled EVM instructions in human-readable format.

View file

@ -237,10 +237,7 @@ func (c *Compiler) pushBin(v interface{}) {
// isPush returns whether the string op is either any of
// push(N).
func isPush(op string) bool {
if op == "push" {
return true
}
return false
return op == "push"
}
// isJump returns whether the string op is jump(i)

View file

@ -170,7 +170,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
}
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.
// State and blocks are stored in the same DB.

View file

@ -35,7 +35,7 @@ func TestHeaderVerification(t *testing.T) {
testdb, _ = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig}
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))
for i, block := range blocks {
@ -87,7 +87,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
testdb, _ = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig}
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))
seals := make([]bool, len(blocks))
@ -159,7 +159,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
testdb, _ = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig}
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))
seals := make([]bool, len(blocks))

View file

@ -57,7 +57,7 @@ func newTestBlockChain(fake bool) *BlockChain {
// 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)) {
// 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 {
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
)
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 {
t.Fatalf("failed to insert forking chain: %v", err)
}
} 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 {
t.Fatalf("failed to insert forking chain: %v", err)
}
@ -186,7 +186,7 @@ func TestLastBlock(t *testing.T) {
bchain := newTestBlockChain(false)
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)
if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
t.Errorf("Write/Get HeadBlockHash failed")
@ -202,7 +202,7 @@ func testExtendCanonical(t *testing.T, full bool) {
length := 5
// Make first chain starting from genesis
_, processor, err := newCanonical(length, full)
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err)
}
@ -230,7 +230,7 @@ func testShorterFork(t *testing.T, full bool) {
length := 10
// Make first chain starting from genesis
_, processor, err := newCanonical(length, full)
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err)
}
@ -260,7 +260,7 @@ func testLongerFork(t *testing.T, full bool) {
length := 10
// Make first chain starting from genesis
_, processor, err := newCanonical(length, full)
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err)
}
@ -290,7 +290,7 @@ func testEqualFork(t *testing.T, full bool) {
length := 10
// Make first chain starting from genesis
_, processor, err := newCanonical(length, full)
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil {
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) {
// Make chain starting from genesis
db, blockchain, err := newCanonical(10, full)
db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full)
if err != nil {
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
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 {
t.Errorf("broken block chain not reported")
}
} 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 {
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) {
for i := 1; i < 25 && !t.Failed(); i++ {
// Create a pristine chain and database
db, blockchain, err := newCanonical(0, full)
db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
if err != nil {
t.Fatalf("failed to create pristine chain: %v", err)
}
@ -536,7 +536,7 @@ func testInsertNonceError(t *testing.T, full bool) {
failNum uint64
)
if full {
blocks := makeBlockChain(blockchain.CurrentBlock(), i, db, 0)
blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0)
failAt = rand.Int() % len(blocks)
failNum = blocks[failAt].NumberU64()
@ -544,7 +544,7 @@ func testInsertNonceError(t *testing.T, full bool) {
blockchain.engine = ethash.NewFakeFailer(failNum)
failRes, err = blockchain.InsertChain(blocks)
} else {
headers := makeHeaderChain(blockchain.CurrentHeader(), i, db, 0)
headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0)
failAt = rand.Int() % len(headers)
failNum = headers[failAt].Number.Uint64()
@ -588,7 +588,7 @@ func TestFastVsFullChains(t *testing.T) {
genesis = gspec.MustCommit(gendb)
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})
// 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)
)
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
remove := []common.Hash{}
@ -781,7 +781,7 @@ func TestChainTxReorgs(t *testing.T) {
// - futureAdd: transaction added after the reorg has already finished
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 {
case 0:
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()
// 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 {
case 0:
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)
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 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
if err != nil {
@ -888,7 +888,7 @@ func TestLogReorgs(t *testing.T) {
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 {
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{})
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 {
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)
if i == 2 {
gen.OffsetTime(-9)
@ -992,7 +992,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
bc := newTestBlockChain(true)
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
pend.Add(len(chain))
@ -1046,7 +1046,7 @@ func TestEIP155Transition(t *testing.T) {
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
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 (
tx *types.Transaction
err error
@ -1109,7 +1109,7 @@ func TestEIP155Transition(t *testing.T) {
// generate an invalid chain id transaction
config := &params.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 (
tx *types.Transaction
err error
@ -1154,7 +1154,7 @@ func TestEIP161AccountRemoval(t *testing.T) {
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
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 (
tx *types.Transaction
err error

View file

@ -21,7 +21,7 @@ import (
"math/big"
"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/core/state"
"github.com/ethereum/go-ethereum/core/types"
@ -42,6 +42,7 @@ type BlockGen struct {
i int
parent *types.Block
chain []*types.Block
chainReader consensus.ChainReader
header *types.Header
statedb *state.StateDB
@ -51,6 +52,7 @@ type BlockGen struct {
uncles []*types.Header
config *params.ChainConfig
engine consensus.Engine
}
// 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 {
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
@ -156,44 +158,54 @@ func (b *BlockGen) OffsetTime(seconds int64) {
// Blocks created by GenerateChain do not contain valid proof of work
// values. Inserting them into BlockChain requires use of FakePow or
// 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 {
config = params.TestChainConfig
}
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
genblock := func(i int, h *types.Header, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb, config: config}
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
// 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
if daoBlock := config.DAOForkBlock; daoBlock != nil {
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 {
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)
}
// Execute any user modifications to the block and finalize it
if gen != nil {
gen(i, b)
}
ethash.AccumulateRewards(config, statedb, h, b.uncles)
root, err := statedb.CommitTo(db, config.IsEIP158(h.Number))
if b.engine != nil {
block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
// 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))
}
h.Root = root
return types.NewBlock(h, b.txs, b.uncles, b.receipts), b.receipts
return block, b.receipts
}
return nil, nil
}
for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabase(db))
if err != nil {
panic(err)
}
header := makeHeader(config, parent, statedb)
block, receipt := genblock(i, header, statedb)
block, receipt := genblock(i, parent, statedb)
blocks[i] = block
receipts[i] = receipt
parent = block
@ -201,7 +213,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, db ethdb.Dat
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
if parent.Time() == nil {
time = big.NewInt(10)
@ -210,10 +222,10 @@ func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.St
}
return &types.Header{
Root: state.IntermediateRoot(config.IsEIP158(parent.Number())),
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(),
Difficulty: ethash.CalcDifficulty(config, time.Uint64(), &types.Header{
Difficulty: engine.CalcDifficulty(chain, time.Uint64(), &types.Header{
Number: parent.Number(),
Time: new(big.Int).Sub(time, big.NewInt(10)),
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
// chain. Depending on the full flag, if creates either a full block chain or a
// 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
gspec := new(Genesis)
db, _ := ethdb.NewMemDatabase()
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
if n == 0 {
return db, blockchain, nil
}
if full {
// Full block-chain requested
blocks := makeBlockChain(genesis, n, db, canonicalSeed)
blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
_, err := blockchain.InsertChain(blocks)
return db, blockchain, err
}
// Header-only chain requested
headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed)
headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
_, err := blockchain.InsertHeaderChain(headers, 1)
return db, blockchain, err
}
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header {
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, db, seed)
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
headers := make([]*types.Header, len(blocks))
for i, block := range blocks {
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.
func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block {
blocks, _ := GenerateChain(params.TestChainConfig, parent, db, n, func(i int, b *BlockGen) {
func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block {
blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
})
return blocks

View file

@ -50,7 +50,7 @@ func ExampleGenerateChain() {
// each block and adds different features to gen based on the
// block index.
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 {
case 0:
// In block 1, addr1 sends addr2 some ether.

View file

@ -35,7 +35,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
db, _ := ethdb.NewMemDatabase()
gspec := new(Genesis)
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
proDb, _ := ethdb.NewMemDatabase()
@ -79,12 +79,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil {
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 {
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
}
// 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 {
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 {
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 {
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
}
// 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 {
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 {
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 {
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 {
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 {
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

View file

@ -103,7 +103,7 @@ var (
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
const (
@ -199,7 +199,7 @@ type TxPool struct {
pendingState *state.ManagedState // Pending state tracking virtual nonces
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
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
// trnsactions from the network.
// transactions from the network.
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
// Sanitize the input to ensure no vulnerable gas prices are set
config = (&config).sanitize()
@ -360,7 +360,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
newNum := newHead.Number.Uint64()
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 {
// Reorg seems shallow enough to pull in all transactions into memory
var discarded, included types.Transactions
@ -838,7 +838,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
for i, hash := range hashes {
if tx := pool.all[hash]; tx != nil {
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
} else {
status[i] = TxStatusQueued

View file

@ -1563,6 +1563,63 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
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
// transaction pool.
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }

View file

@ -137,7 +137,7 @@ func isProtectedV(V *big.Int) bool {
return true
}
// DecodeRLP implements rlp.Encoder
// EncodeRLP implements rlp.Encoder
func (tx *Transaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &tx.data)
}
@ -153,6 +153,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
return err
}
// MarshalJSON encodes the web3 RPC transaction format.
func (tx *Transaction) MarshalJSON() ([]byte, error) {
hash := tx.Hash()
data := tx.data
@ -168,8 +169,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
}
var V byte
if isProtectedV(dec.V) {
chainId := deriveChainId(dec.V).Uint64()
V = byte(dec.V.Uint64() - 35 - 2*chainId)
chainID := deriveChainId(dec.V).Uint64()
V = byte(dec.V.Uint64() - 35 - 2*chainID)
} else {
V = byte(dec.V.Uint64() - 27)
}
@ -192,10 +193,9 @@ func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) To() *common.Address {
if tx.data.Recipient == nil {
return nil
} else {
}
to := *tx.data.Recipient
return &to
}
}
// 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
// Len returns the length of s
// Len returns the length of 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] }
// 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 {
enc, _ := rlp.EncodeToBytes(s[i])
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) {
keep = make(Transactions, 0, len(a))
@ -378,7 +378,7 @@ func (s *TxByPrice) Pop() interface{} {
}
// 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.
type TransactionsByPriceAndNonce struct {
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions

View file

@ -19,6 +19,7 @@ package vm
import (
"math/big"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"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.
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 {
precompiles := PrecompiledContractsHomestead
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 evm.interpreter.Run(snapshot, contract, input)
return evm.interpreter.Run(contract, input)
}
// 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)
// initialise a new contract and set the code that is to be used by the
// E The contract is a scoped environment for this execution context
// only.
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, value, gas)
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
// 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.
@ -215,7 +226,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
contract := NewContract(caller, to, value, gas)
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 {
evm.StateDB.RevertToSnapshot(snapshot)
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.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 {
evm.StateDB.RevertToSnapshot(snapshot)
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
// 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.
ret, err = run(evm, snapshot, contract, input)
ret, err = run(evm, contract, input)
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
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 {
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
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
// 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 {
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
}

View file

@ -107,9 +107,9 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
// the return byte-slice and an error if one occurred.
//
// 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
// should be handled to reduce complexity and errors further down the in.
func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret []byte, err error) {
// considered a revert-and-consume-all-gas operation except for
// errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024
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
if in.cfg.Debug {
defer func() {
if err != nil && !logged && in.cfg.Debug {
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
// 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

View file

@ -84,7 +84,9 @@ func (s *StructLog) OpName() string {
// Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call.
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
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
}
@ -111,6 +113,10 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
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 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
}
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 {
fmt.Printf("0x%x", output)
if err != nil {

View file

@ -20,7 +20,7 @@ var curveB = new(big.Int).SetInt64(3)
// curveGen is the generator of G₁.
var curveGen = &curvePoint{
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),
}

View file

@ -79,7 +79,7 @@ func ToECDSA(d []byte) (*ecdsa.PrivateKey, error) {
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
// errors due to bad origin encoding (0 prefixes cut off).
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.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
}

View file

@ -20,12 +20,10 @@ import (
"bytes"
"crypto/ecdsa"
"encoding/hex"
"fmt"
"io/ioutil"
"math/big"
"os"
"testing"
"time"
"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)
}
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) {
a := []byte("hello world")
amount := 1000000
start := time.Now()
for i := 0; i < amount; i++ {
for i := 0; i < b.N; i++ {
Keccak256(a)
}
fmt.Println(amount, ":", time.Since(start))
}
func TestSign(t *testing.T) {

View file

@ -34,7 +34,6 @@ package secp256k1
import (
"crypto/elliptic"
"math/big"
"sync"
"unsafe"
"github.com/ethereum/go-ethereum/common/math"
@ -42,7 +41,7 @@ import (
/*
#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"
@ -236,7 +235,7 @@ func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int,
math.ReadBits(By, point[32:])
pointPtr := (*C.uchar)(unsafe.Pointer(&point[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.
x := new(big.Int).SetBytes(point[:32])
@ -263,14 +262,10 @@ func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
// X9.62.
func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
byteLen := (BitCurve.BitSize + 7) >> 3
ret := make([]byte, 1+2*byteLen)
ret[0] = 4 // uncompressed point
xBytes := x.Bytes()
copy(ret[1+byteLen-len(xBytes):], xBytes)
yBytes := y.Bytes()
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
ret[0] = 4 // uncompressed point flag
math.ReadBits(x, ret[1:1+byteLen])
math.ReadBits(y, ret[1+byteLen:])
return ret
}
@ -289,24 +284,21 @@ func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
return
}
var (
initonce sync.Once
theCurve *BitCurve
)
var theCurve = new(BitCurve)
// S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1)
func S256() *BitCurve {
initonce.Do(func() {
func init() {
// 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
})
}
// S256 returns a BitCurve which implements secp256k1.
func S256() *BitCurve {
return theCurve
}

View file

@ -19,7 +19,7 @@ static secp256k1_context* secp256k1_context_create_sign_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
// 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)
// 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)
static int secp256k1_ecdsa_recover_pubkey(
static int secp256k1_ext_ecdsa_recover(
const secp256k1_context* ctx,
unsigned char *pubkey_out,
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);
}
// 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
// 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,
// encoded as two 256bit big-endian numbers.
// 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 overflow = 0;
secp256k1_fe feX, feY;

View file

@ -38,6 +38,7 @@ import "C"
import (
"errors"
"math/big"
"unsafe"
)
@ -55,6 +56,7 @@ var (
ErrInvalidSignatureLen = errors.New("invalid signature length")
ErrInvalidRecoveryID = errors.New("invalid signature recovery id")
ErrInvalidKey = errors.New("invalid private key")
ErrInvalidPubkey = errors.New("invalid public key")
ErrSignFailed = errors.New("signing 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]))
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 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 {
if len(sig) != 65 {
return ErrInvalidSignatureLen

View file

@ -27,10 +27,12 @@ import (
"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) {
return secp256k1.RecoverPubkey(hash, sig)
}
// SigToPub returns the public key that created the given signature.
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
s, err := Ecrecover(hash, sig)
if err != nil {
@ -58,6 +60,27 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
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.
func S256() elliptic.Curve {
return secp256k1.S256()

View file

@ -21,11 +21,14 @@ package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"errors"
"fmt"
"math/big"
"github.com/btcsuite/btcd/btcec"
)
// Ecrecover returns the uncompressed public key that created the given signature.
func Ecrecover(hash, sig []byte) ([]byte, error) {
pub, err := SigToPub(hash, sig)
if err != nil {
@ -35,6 +38,7 @@ func Ecrecover(hash, sig []byte) ([]byte, error) {
return bytes, err
}
// SigToPub returns the public key that created the given signature.
func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) {
// Convert to btcec input format with 'recovery id' v at the beginning.
btcsig := make([]byte, 65)
@ -71,6 +75,42 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) {
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.
func S256() elliptic.Curve {
return btcec.S256()

View file

@ -18,19 +18,143 @@ package crypto
import (
"bytes"
"encoding/hex"
"crypto/ecdsa"
"reflect"
"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) {
msg, _ := hex.DecodeString("ce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008")
sig, _ := hex.DecodeString("90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301")
pubkey1, _ := hex.DecodeString("04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652")
pubkey2, err := Ecrecover(msg, sig)
var (
testmsg = hexutil.MustDecode("0xce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008")
testsig = hexutil.MustDecode("0x90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301")
testpubkey = hexutil.MustDecode("0x04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652")
testpubkeyc = hexutil.MustDecode("0x02e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a")
)
func TestEcrecover(t *testing.T) {
pubkey, err := Ecrecover(testmsg, testsig)
if err != nil {
t.Fatalf("recover error: %s", err)
}
if !bytes.Equal(pubkey1, pubkey2) {
t.Errorf("pubkey mismatch: want: %x have: %x", pubkey1, pubkey2)
if !bytes.Equal(pubkey, testpubkey) {
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)
}
}
}

View file

@ -9,10 +9,11 @@ The client's UI uses [React][React] with JSX syntax, which is validated by the [
### 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 && ./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:
@ -22,13 +23,20 @@ $ (cd dashboard/assets && ./node_modules/.bin/webpack --watch)
$ 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
```
### 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
[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/
[WV]: http://chrisbateman.github.io/webpack-visualizer/
[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

File diff suppressed because one or more lines are too long

View file

@ -16,37 +16,68 @@
// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
{
"plugins": [
"react"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true,
"modules": true
'env': {
'browser': true,
'node': true,
'es6': true,
},
'parser': 'babel-eslint',
'parserOptions': {
'sourceType': 'module',
'ecmaVersion': 6,
'ecmaFeatures': {
'jsx': true,
}
},
"rules": {
"react/prefer-es6-class": 2,
"react/prefer-stateless-function": 2,
"react/jsx-pascal-case": 2,
"react/jsx-closing-bracket-location": [1, {"selfClosing": "tag-aligned", "nonEmpty": "tag-aligned"}],
"react/jsx-closing-tag-location": 1,
"jsx-quotes": ["error", "prefer-double"],
"no-multi-spaces": "error",
"react/jsx-tag-spacing": 2,
"react/jsx-curly-spacing": [2, {"when": "never", "children": true}],
"react/jsx-boolean-value": 2,
"react/no-string-refs": 2,
"react/jsx-wrap-multilines": 2,
"react/self-closing-comp": 2,
"react/jsx-no-bind": 2,
"react/require-render-return": 2,
"react/no-is-mounted": 2,
"key-spacing": ["error", {"align": {
"beforeColon": false,
"afterColon": true,
"on": "value"
}}]
'extends': 'airbnb',
'plugins': [
'flowtype',
'react',
],
'rules': {
'no-tabs': 'off',
'indent': ['error', 'tab'],
'react/jsx-indent': ['error', 'tab'],
'react/jsx-indent-props': ['error', 'tab'],
'react/prefer-stateless-function': 'off',
// Specifies the maximum length of a line.
'max-len': ['warn', 120, 2, {
'ignoreUrls': true,
'ignoreComments': false,
'ignoreRegExpLiterals': true,
'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,
}
},
}

View 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

View 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);

View 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;

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// 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
// 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.
export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
export const LIMIT = {
memory: 200, // Maximum number of memory data samples.
traffic: 200, // Maximum number of traffic data samples.
log: 200, // Maximum number of logs.
};
type ProvidedMenuProp = {|title: string, icon: string|};
const menuSkeletons: Array<{|id: string, menu: ProvidedMenuProp|}> = [
{
id: 'home',
menu: {
title: 'Home',
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.
export const TAGS = (() => {
const T = {
home: { title: "Home", },
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;
})();
// Using the id is circumstantial in some cases, so it is better to insert it also as a value.
// This way the mistyping is prevented.
export const MENU: Map<string, {...MenuProp}> = new Map(menuSkeletons.map(({id, menu}) => ([id, {id, ...menu}])));
export const DATA_KEYS = (() => {
const DK = {};
["memory", "traffic", "logs"].map(key => {
DK[key] = key;
});
return DK;
})();
type ProvidedSampleProp = {|limit: number|};
const sampleSkeletons: Array<{|id: string, sample: ProvidedSampleProp|}> = [
{
id: 'memory',
sample: {
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 DRAWER_WIDTH = 240;
export const DURATION = 200;
export const LENS: Map<string, string> = new Map([
'content',
...menuSkeletons.map(({id}) => id),
...sampleSkeletons.map(({id}) => id),
].map(lens => [lens, lens]));

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
@ -15,37 +17,69 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withStyles} from 'material-ui/styles';
import SideBar from './SideBar.jsx';
import Header from './Header.jsx';
import Main from "./Main.jsx";
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
import withStyles from 'material-ui/styles/withStyles';
import {lensPath, view, set} from 'ramda';
// 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 => ({
appFrame: {
position: 'relative',
dashboard: {
display: 'flex',
flexFlow: 'column',
width: '100%',
height: '100%',
background: theme.palette.background.default,
zIndex: 1,
overflow: 'hidden',
},
});
// 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 {
constructor(props) {
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: TAGS.home.id, // active menu
sideBar: true, // true if the sidebar is opened
memory: [],
traffic: [],
logs: [],
shouldUpdate: {},
active: MENU.get('home').id,
sideBar: true,
content: {home: {memory: [], traffic: []}, logs: {log: []}},
shouldUpdate: new Set(),
};
}
@ -57,104 +91,104 @@ class Dashboard extends Component {
// reconnect establishes a websocket connection with the server, listens for incoming messages
// and tries to reconnect on connection loss.
reconnect = () => {
const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
server.onmessage = event => {
const msg = JSON.parse(event.data);
if (isNullOrUndefined(msg)) {
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);
};
};
// update analyzes the incoming message, and updates the charts' content correspondingly.
update = msg => {
console.log(msg);
this.setState(prevState => {
let newState = [];
newState.shouldUpdate = {};
const insert = (key, values, limit) => {
newState[key] = [...prevState[key], ...values];
while (newState[key].length > limit) {
newState[key].shift();
// samples retrieves the raw data of a chart field from the incoming message.
samples = (chart: Chart) => {
let s = [];
if (chart.history) {
s = chart.history.map(({value}) => (value || 0)); // traffic comes without value at the beginning
}
newState.shouldUpdate[key] = true;
if (chart.new) {
s = [...s, chart.new.value || 0];
}
return s;
};
// (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);
}
// handleHome changes the home-menu related part of the state.
handleHome = (home: HomeMessage) => {
this.setState((prevState) => {
let newState = prevState;
newState.shouldUpdate = new Set();
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;
});
};
// The change of the active label on the SideBar component will trigger a new render in the Main component.
changeContent = active => {
this.setState(prevState => prevState.active !== active ? {active: active} : {});
// handleLogs changes the logs-menu related part of the state.
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 = (msg: Message) => {
if (msg.home) {
this.handleHome(msg.home);
}
if (msg.logs) {
this.handleLogs(msg.logs);
}
};
// changeContent sets the active label, which is used at the content rendering.
changeContent = (newActive: string) => {
this.setState(prevState => (prevState.active !== newActive ? {active: newActive} : {}));
};
// openSideBar opens the sidebar.
openSideBar = () => {
this.setState({sideBar: true});
};
// closeSideBar closes the sidebar.
closeSideBar = () => {
this.setState({sideBar: false});
};
render() {
// The classes property is injected by withStyles().
const {classes} = this.props;
const {classes} = this.props; // The classes property is injected by withStyles().
return (
<div className={classes.appFrame}>
<div className={classes.dashboard}>
<Header
opened={this.state.sideBar}
open={this.openSideBar}
openSideBar={this.openSideBar}
closeSideBar={this.closeSideBar}
/>
<SideBar
<Body
opened={this.state.sideBar}
close={this.closeSideBar}
changeContent={this.changeContent}
/>
<Main
opened={this.state.sideBar}
active={this.state.active}
memory={this.state.memory}
traffic={this.state.traffic}
logs={this.state.logs}
content={this.state.content}
shouldUpdate={this.state.shouldUpdate}
/>
</div>
@ -162,8 +196,4 @@ class Dashboard extends Component {
}
}
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Dashboard);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
@ -15,61 +17,83 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {withStyles} from 'material-ui/styles';
import withStyles from 'material-ui/styles/withStyles';
import AppBar from 'material-ui/AppBar';
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 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.
const styles = theme => ({
appBar: {
position: 'absolute',
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
header: {
backgroundColor: theme.palette.background.appBar,
color: theme.palette.getContrastText(theme.palette.background.appBar),
zIndex: theme.zIndex.appBar,
},
appBarShift: {
marginLeft: DRAWER_WIDTH,
width: `calc(100% - ${DRAWER_WIDTH}px)`,
transition: theme.transitions.create(['margin', 'width'], {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
toolbar: {
paddingLeft: theme.spacing.unit,
paddingRight: theme.spacing.unit,
},
menuButton: {
marginLeft: 12,
marginRight: 20,
},
hide: {
display: 'none',
mainText: {
paddingLeft: theme.spacing.unit,
},
});
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;
}
// changeSideBar opens or closes the sidebar corresponding to the previous state.
changeSideBar = () => {
if (this.props.opened) {
this.props.closeSideBar();
} else {
this.props.openSideBar();
}
};
// arrowButton is connected to the sidebar; changes its state.
arrowButton = (transitionState: string) => (
<IconButton onClick={this.changeSideBar}>
<ChevronLeftIcon
style={{
...arrowDefault,
...arrowTransition[transitionState],
}}
/>
</IconButton>
);
// Header renders a header, which contains a sidebar opener icon when that is closed.
class Header extends Component {
render() {
// The classes property is injected by withStyles().
const {classes} = this.props;
const {classes, opened} = this.props; // The classes property is injected by withStyles().
return (
<AppBar className={classNames(classes.appBar, this.props.opened && classes.appBarShift)}>
<Toolbar disableGutters={!this.props.opened}>
<IconButton
color="contrast"
aria-label="open drawer"
onClick={this.props.open}
className={classNames(classes.menuButton, this.props.opened && classes.hide)}
>
<MenuIcon />
</IconButton>
<Typography type="title" color="inherit" noWrap>
<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>
@ -78,10 +102,4 @@ class Header extends Component {
}
}
Header.propTypes = {
classes: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
open: PropTypes.func.isRequired,
};
export default withStyles(styles)(Header);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// 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/>.
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.
// The children are Recharts components extended with the Material-UI's xs property.
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>
);
}
}
import ChartGrid from './ChartGrid';
import type {ChartEntry} from '../types/message';
ChartGrid.propTypes = {
spacing: PropTypes.number.isRequired,
export type Props = {
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.
class Home extends Component {
shouldComponentUpdate(nextProps) {
return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) ||
!isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]);
return nextProps.shouldUpdate.has('memory') || nextProps.shouldUpdate.has('traffic');
}
render() {
const {theme} = this.props;
const memoryColor = theme.palette.primary[300];
const trafficColor = theme.palette.secondary[300];
const {memory, traffic} = this.props;
return (
<ChartGrid spacing={24}>
<AreaChart xs={6} height={300} values={this.props.memory}>
<AreaChart xs={6} height={300} values={memory}>
<YAxis />
<Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} />
<Area type="monotone" dataKey="value" stroke={this.memoryColor} fill={this.memoryColor} />
</AreaChart>
<LineChart xs={6} height={300} values={this.props.traffic}>
<Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} />
<LineChart xs={6} height={300} values={traffic}>
<Line type="monotone" dataKey="value" stroke={this.trafficColor} dot={false} />
</LineChart>
<LineChart xs={6} height={300} values={this.props.memory}>
<LineChart xs={6} height={300} values={memory}>
<YAxis />
<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>
<AreaChart xs={6} height={300} values={this.props.traffic}>
<AreaChart xs={6} height={300} values={traffic}>
<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>
</ChartGrid>
);
}
}
Home.propTypes = {
theme: PropTypes.object.isRequired,
shouldUpdate: PropTypes.object.isRequired,
};
export default withTheme()(Home);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// 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/>.
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 Home from './Home.jsx';
import withStyles from 'material-ui/styles/withStyles';
// ContentSwitch chooses and renders the proper page content.
class ContentSwitch extends Component {
render() {
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;
}
}
import Home from './Home';
import {MENU} from './Common';
import type {Content} from '../types/content';
ContentSwitch.propTypes = {
active: PropTypes.string.isRequired,
shouldUpdate: PropTypes.object.isRequired,
};
// styles contains the styles for the Main component.
// Styles for the Content component.
const styles = theme => ({
content: {
width: '100%',
marginLeft: -DRAWER_WIDTH,
flexGrow: 1,
backgroundColor: theme.palette.background.default,
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,
}),
},
});
// Main renders a component for the page content.
class Main extends Component {
export type Props = {
classes: Object,
active: string,
content: Content,
shouldUpdate: Object,
};
// Main renders the chosen content.
class Main extends Component<Props> {
render() {
// The classes property is injected by withStyles().
const {classes} = this.props;
const {
classes, active, content, shouldUpdate,
} = 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>
);
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>;
}
}
Main.propTypes = {
classes: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
active: PropTypes.string.isRequired,
shouldUpdate: PropTypes.object.isRequired,
};
export default withStyles(styles)(Main);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
@ -15,37 +17,42 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
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.
const styles = theme => ({
drawerPaper: {
position: 'relative',
height: '100%',
width: DRAWER_WIDTH,
list: {
background: theme.palette.background.appBar,
},
drawerHeader: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
transitionDuration: {
enter: theme.transitions.duration.enteringScreen,
exit: theme.transitions.duration.leavingScreen,
}
listItem: {
minWidth: theme.spacing.unit * 3,
},
icon: {
fontSize: theme.spacing.unit * 3,
},
});
// SideBar renders a sidebar component.
class SideBar extends Component {
export type Props = {
classes: Object,
opened: boolean,
changeContent: () => {},
};
// SideBar renders the sidebar of the dashboard.
class SideBar extends Component<Props> {
constructor(props) {
super(props);
@ -53,54 +60,63 @@ class SideBar extends Component {
// Instantiate only once, and reuse the existing functions to prevent the creation of
// 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 => {
MENU.forEach((menu) => {
this.clickOn[menu.id] = (event) => {
event.preventDefault();
console.log(event.target.key);
this.props.changeContent(id);
props.changeContent(menu.id);
};
}
});
}
render() {
// The classes property is injected by withStyles().
shouldComponentUpdate(nextProps) {
return nextProps.opened !== this.props.opened;
}
menuItems = (transitionState) => {
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;
};
// menu renders the list of the menu items.
menu = (transitionState) => {
const {classes} = this.props; // The classes property is injected by withStyles().
return (
<Drawer
type="persistent"
classes={{paper: classes.drawerPaper,}}
open={this.props.opened}
>
<div>
<div className={classes.drawerHeader}>
<IconButton onClick={this.props.close}>
<ChevronLeftIcon />
</IconButton>
</div>
<div className={classes.list}>
<List>
{
Object.values(TAGS).map(tag => {
return (
<ListItem button key={tag.id} onClick={this.clickOn[tag.id]}>
<ListItemText primary={tag.title} />
</ListItem>
);
})
}
{this.menuItems(transitionState)}
</List>
</div>
</Drawer>
);
};
render() {
return (
<Transition mountOnEnter in={this.props.opened} timeout={{enter: DURATION}}>
{this.menu}
</Transition>
);
}
}
SideBar.propTypes = {
classes: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
changeContent: PropTypes.func.isRequired,
};
export default withStyles(styles)(SideBar);

View 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)[^;]*;/,';');
};

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors
// 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/>.
import React from 'react';
import {hydrate} from 'react-dom';
import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles';
import {render} from 'react-dom';
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({
palette: {
type: 'dark',
},
});
// Renders the whole dashboard.
hydrate(
const dashboard = document.getElementById('dashboard');
if (dashboard) {
// Renders the whole dashboard.
render(
<MuiThemeProvider theme={theme}>
<Dashboard />
</MuiThemeProvider>,
document.getElementById('dashboard')
);
dashboard,
);
}

6806
dashboard/assets/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,22 +1,41 @@
{
"dependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.1",
"babel-eslint": "^8.0.3",
"babel-loader": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"babel-runtime": "^6.26.0",
"classnames": "^2.2.5",
"eslint": "^4.5.0",
"eslint-plugin-react": "^7.4.0",
"material-ui": "^1.0.0-beta.18",
"css-loader": "^0.28.7",
"eslint": "^4.13.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-loader": "^1.9.0",
"eslint-plugin-import": "^2.8.0",
"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",
"prop-types": "^15.6.0",
"recharts": "^1.0.0-beta.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"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",
"webpack": "^3.5.5"
"url-loader": "^0.6.2",
"webpack": "^3.10.0"
}
}

View file

@ -6,9 +6,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Go Ethereum Dashboard</title>
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico"/>
<!-- TODO (kurkomisi): Return to the external libraries to speed up the bundling during development -->
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico" />
<style>
::-webkit-scrollbar {
width: 16px;
}
::-webkit-scrollbar-thumb {
background: #212121;
}
</style>
</head>
<body style="height: 100%; margin: 0">
<div id="dashboard" style="height: 100%"></div>

View 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>,
};

View 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,
};

View file

@ -14,22 +14,60 @@
// 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/>.
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './index.jsx',
resolve: {
extensions: ['.js', '.jsx'],
},
entry: './index',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js',
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
comments: false,
mangle: false,
beautify: true,
}),
],
module: {
loaders: [
rules: [
{
test: /\.jsx$/, // regexp for JSX files
loader: 'babel-loader', // The babel configuration is in the package.json.
query: {
presets: ['env', 'react', 'stage-0']
}
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',
},
],
},

View file

@ -16,7 +16,10 @@
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 (
"fmt"
@ -40,7 +43,7 @@ const (
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.
type Dashboard struct {
@ -48,46 +51,30 @@ type Dashboard struct {
listener net.Listener
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
quit chan chan error // Channel used for graceful exit
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.
type client struct {
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
}
// 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.
func New(config *Config) (*Dashboard, error) {
return &Dashboard{
conns: make(map[uint32]*client),
config: config,
quit: make(chan chan error),
charts: &HomeMessage{
Memory: &Chart{},
Traffic: &Chart{},
},
}, nil
}
@ -183,13 +170,13 @@ func (db *Dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
// apiHandler handles requests for the dashboard.
func (db *Dashboard) apiHandler(conn *websocket.Conn) {
id := atomic.AddUint32(&nextId, 1)
id := atomic.AddUint32(&nextID, 1)
client := &client{
conn: conn,
msg: make(chan message, 128),
msg: make(chan Message, 128),
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.
db.wg.Add(1)
@ -210,8 +197,15 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
}
}()
// Send the past data.
client.msg <- message{
History: &db.charts,
client.msg <- Message{
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.
db.lock.Lock()
@ -245,29 +239,34 @@ func (db *Dashboard) collectData() {
inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
now := time.Now()
memory := &chartEntry{
memory := &ChartEntry{
Time: now,
Value: memoryInUse,
}
traffic := &chartEntry{
traffic := &ChartEntry{
Time: now,
Value: inboundTraffic,
}
// Remove the first elements in case the samples' amount exceeds the limit.
first := 0
if len(db.charts.Memory) == memorySampleLimit {
if len(db.charts.Memory.History) == memorySampleLimit {
first = 1
}
db.charts.Memory = append(db.charts.Memory[first:], memory)
db.charts.Memory.History = append(db.charts.Memory.History[first:], memory)
first = 0
if len(db.charts.Traffic) == trafficSampleLimit {
if len(db.charts.Traffic.History) == trafficSampleLimit {
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{
Memory: memory,
Traffic: traffic,
db.sendToAll(&Message{
Home: &HomeMessage{
Memory: &Chart{
New: memory,
},
Traffic: &Chart{
New: traffic,
},
},
})
}
}
@ -277,6 +276,7 @@ func (db *Dashboard) collectData() {
func (db *Dashboard) collectLogs() {
defer db.wg.Done()
id := 1
// TODO (kurkomisi): log collection comes here.
for {
select {
@ -284,15 +284,18 @@ func (db *Dashboard) collectLogs() {
errc <- nil
return
case <-time.After(db.config.Refresh / 2):
db.sendToAll(&message{
Log: "This is a fake log.",
db.sendToAll(&Message{
Logs: &LogsMessage{
Log: fmt.Sprintf("%-4d: This is a fake log.", id),
},
})
id++
}
}
}
// sendToAll sends the given message to the active dashboards.
func (db *Dashboard) sendToAll(msg *message) {
func (db *Dashboard) sendToAll(msg *Message) {
db.lock.Lock()
for _, c := range db.conns {
select {

63
dashboard/message.go Normal file
View 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"`
}

View file

@ -17,24 +17,19 @@
package eth
import (
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"io/ioutil"
"math/big"
"os"
"strings"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/miner"
"github.com/ethereum/go-ethereum/params"
@ -43,8 +38,6 @@ import (
"github.com/ethereum/go-ethereum/trie"
)
const defaultTraceTimeout = 5 * time.Second
// PublicEthereumAPI provides an API to access Ethereum full node-related
// information.
type PublicEthereumAPI struct {
@ -348,238 +341,6 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug
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 &ethapi.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.
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
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.
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 {
return StorageRangeResult{}, err
}
@ -615,14 +376,18 @@ func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common
if st == nil {
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))
result := StorageRangeResult{Storage: storageMap{}}
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 {
preimage := common.BytesToHash(preimage)
e.Key = &preimage
@ -634,7 +399,7 @@ func storageRangeAt(st state.Trie, start []byte, maxResult int) StorageRangeResu
next := common.BytesToHash(it.Key)
result.NextKey = &next
}
return result
return result, nil
}
// GetModifiedAccountsByumber returns all accounts that have changed between the

View file

@ -79,7 +79,10 @@ func TestStorageRangeAt(t *testing.T) {
},
}
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) {
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))

Some files were not shown because too many files have changed in this diff Show more