Upgrade Callisto client from upstream

This commit is contained in:
Yohan Graterol 2017-12-26 16:37:54 -05:00
commit e628f04131
165 changed files with 161500 additions and 1658 deletions

1
.gitignore vendored
View file

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

View file

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

View file

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

View file

@ -19,6 +19,8 @@ package abi
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect"
"strings"
) )
// Argument holds the name of the argument and the corresponding type. // Argument holds the name of the argument and the corresponding type.
@ -29,7 +31,10 @@ type Argument struct {
Indexed bool // indexed is only used by events Indexed bool // indexed is only used by events
} }
func (a *Argument) UnmarshalJSON(data []byte) error { type Arguments []Argument
// UnmarshalJSON implements json.Unmarshaler interface
func (argument *Argument) UnmarshalJSON(data []byte) error {
var extarg struct { var extarg struct {
Name string Name string
Type string Type string
@ -40,12 +45,180 @@ func (a *Argument) UnmarshalJSON(data []byte) error {
return fmt.Errorf("argument json err: %v", err) return fmt.Errorf("argument json err: %v", err)
} }
a.Type, err = NewType(extarg.Type) argument.Type, err = NewType(extarg.Type)
if err != nil { if err != nil {
return err return err
} }
a.Name = extarg.Name argument.Name = extarg.Name
a.Indexed = extarg.Indexed argument.Indexed = extarg.Indexed
return nil return nil
} }
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
func (arguments Arguments) LengthNonIndexed() int {
out := 0
for _, arg := range arguments {
if !arg.Indexed {
out++
}
}
return out
}
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
func (arguments Arguments) isTuple() bool {
return len(arguments) > 1
}
// Unpack performs the operation hexdata -> Go format
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
if arguments.isTuple() {
return arguments.unpackTuple(v, data)
}
return arguments.unpackAtomic(v, data)
}
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
// make sure the passed value is arguments pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
var (
value = valueOf.Elem()
typ = value.Type()
kind = value.Kind()
)
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err
}
// `i` counts the nonindexed arguments.
// `j` counts the number of complex types.
// both `i` and `j` are used to to correctly compute `data` offset.
i, j := -1, 0
for _, arg := range arguments {
if arg.Indexed {
// can't read, continue
continue
}
i++
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
if err != nil {
return err
}
if arg.Type.T == ArrayTy {
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
// we need to decrement 'j' because 'i' was incremented
j += arg.Type.Size - 1
}
reflectValue := reflect.ValueOf(marshalledValue)
switch kind {
case reflect.Struct:
for j := 0; j < typ.NumField(); j++ {
field := typ.Field(j)
// TODO read tags: `abi:"fieldName"`
if field.Name == strings.ToUpper(arg.Name[:1])+arg.Name[1:] {
if err := set(value.Field(j), reflectValue, arg); err != nil {
return err
}
}
}
case reflect.Slice, reflect.Array:
if value.Len() < i {
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
}
v := value.Index(i)
if err := requireAssignable(v, reflectValue); err != nil {
return err
}
if err := set(v.Elem(), reflectValue, arg); err != nil {
return err
}
default:
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
}
}
return nil
}
// unpackAtomic unpacks ( hexdata -> go ) a single value
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
// make sure the passed value is arguments pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
arg := arguments[0]
if arg.Indexed {
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
}
value := valueOf.Elem()
marshalledValue, err := toGoType(0, arg.Type, output)
if err != nil {
return err
}
return set(value, reflect.ValueOf(marshalledValue), arg)
}
// Unpack performs the operation Go format -> Hexdata
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
// Make sure arguments match up and pack them
abiArgs := arguments
if len(args) != len(abiArgs) {
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
}
// variable input is the output appended at the end of packed
// output. This is used for strings and bytes types input.
var variableInput []byte
// input offset is the bytes offset for packed output
inputOffset := 0
for _, abiArg := range abiArgs {
if abiArg.Type.T == ArrayTy {
inputOffset += (32 * abiArg.Type.Size)
} else {
inputOffset += 32
}
}
var ret []byte
for i, a := range args {
input := abiArgs[i]
// pack the input
packed, err := input.Type.pack(reflect.ValueOf(a))
if err != nil {
return nil, err
}
// check for a slice type (string, bytes, slice)
if input.Type.requiresLengthPrefix() {
// calculate the offset
offset := inputOffset + len(variableInput)
// set the offset
ret = append(ret, packNum(reflect.ValueOf(offset))...)
// Append the packed output to the variable input. The variable input
// will be appended at the end of the input.
variableInput = append(variableInput, packed...)
} else {
// append the packed value to the input
ret = append(ret, packed...)
}
}
// append the variable input at the end of the packed input
ret = append(ret, variableInput...)
return ret, nil
}

View file

@ -89,7 +89,7 @@ func (b *SimulatedBackend) Rollback() {
} }
func (b *SimulatedBackend) rollback() { func (b *SimulatedBackend) rollback() {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {}) blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
b.pendingBlock = blocks[0] b.pendingBlock = blocks[0]
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database)) b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
} }
@ -291,7 +291,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
} }
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() { for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx) block.AddTx(tx)
} }
@ -306,7 +306,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) { blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() { for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx) block.AddTx(tx)
} }

View file

@ -18,7 +18,6 @@ package abi
import ( import (
"fmt" "fmt"
"reflect"
"strings" "strings"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
@ -31,7 +30,7 @@ import (
type Event struct { type Event struct {
Name string Name string
Anonymous bool Anonymous bool
Inputs []Argument Inputs Arguments
} }
// Id returns the canonical representation of the event's signature used by the // Id returns the canonical representation of the event's signature used by the
@ -45,93 +44,3 @@ func (e Event) Id() common.Hash {
} }
return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) return common.BytesToHash(crypto.Keccak256([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ",")))))
} }
// unpacks an event return tuple into a struct of corresponding go types
//
// Unpacking can be done into a struct or a slice/array.
func (e Event) tupleUnpack(v interface{}, output []byte) error {
// make sure the passed value is a pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
var (
value = valueOf.Elem()
typ = value.Type()
)
if value.Kind() != reflect.Struct {
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
}
j := 0
for i := 0; i < len(e.Inputs); i++ {
input := e.Inputs[i]
if input.Indexed {
// can't read, continue
continue
} else if input.Type.T == ArrayTy {
// need to move this up because they read sequentially
j += input.Type.Size
}
marshalledValue, err := toGoType((i+j)*32, input.Type, output)
if err != nil {
return err
}
reflectValue := reflect.ValueOf(marshalledValue)
switch value.Kind() {
case reflect.Struct:
for j := 0; j < typ.NumField(); j++ {
field := typ.Field(j)
// TODO read tags: `abi:"fieldName"`
if field.Name == strings.ToUpper(e.Inputs[i].Name[:1])+e.Inputs[i].Name[1:] {
if err := set(value.Field(j), reflectValue, e.Inputs[i]); err != nil {
return err
}
}
}
case reflect.Slice, reflect.Array:
if value.Len() < i {
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(e.Inputs), value.Len())
}
v := value.Index(i)
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
}
reflectValue := reflect.ValueOf(marshalledValue)
if err := set(v.Elem(), reflectValue, e.Inputs[i]); err != nil {
return err
}
default:
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
}
}
return nil
}
func (e Event) isTupleReturn() bool { return len(e.Inputs) > 1 }
func (e Event) singleUnpack(v interface{}, output []byte) error {
// make sure the passed value is a pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
if e.Inputs[0].Indexed {
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
}
value := valueOf.Elem()
marshalledValue, err := toGoType(0, e.Inputs[0].Type, output)
if err != nil {
return err
}
if err := set(value, reflect.ValueOf(marshalledValue), e.Inputs[0]); err != nil {
return err
}
return nil
}

View file

@ -17,13 +17,53 @@
package abi package abi
import ( import (
"bytes"
"encoding/hex"
"encoding/json"
"math/big"
"reflect"
"strings" "strings"
"testing" "testing"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/crypto" "github.com/EthereumCommonwealth/go-callisto/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
var jsonEventTransfer = []byte(`{
"anonymous": false,
"inputs": [
{
"indexed": true, "name": "from", "type": "address"
}, {
"indexed": true, "name": "to", "type": "address"
}, {
"indexed": false, "name": "value", "type": "uint256"
}],
"name": "Transfer",
"type": "event"
}`)
var jsonEventPledge = []byte(`{
"anonymous": false,
"inputs": [{
"indexed": false, "name": "who", "type": "address"
}, {
"indexed": false, "name": "wad", "type": "uint128"
}, {
"indexed": false, "name": "currency", "type": "bytes3"
}],
"name": "Pledge",
"type": "event"
}`)
// 1000000
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
func TestEventId(t *testing.T) { func TestEventId(t *testing.T) {
var table = []struct { var table = []struct {
definition string definition string
@ -54,3 +94,223 @@ func TestEventId(t *testing.T) {
} }
} }
} }
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
type testStruct struct {
Value1 [2]uint8
Value2 uint8
}
abi, err := JSON(strings.NewReader(definition))
require.NoError(t, err)
var b bytes.Buffer
var i uint8 = 1
for ; i <= 3; i++ {
b.Write(packNum(reflect.ValueOf(i)))
}
var rst testStruct
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
require.Equal(t, [2]uint8{1, 2}, rst.Value1)
require.Equal(t, uint8(3), rst.Value2)
}
func TestEventTupleUnpack(t *testing.T) {
type EventTransfer struct {
Value *big.Int
}
type EventPledge struct {
Who common.Address
Wad *big.Int
Currency [3]byte
}
type BadEventPledge struct {
Who string
Wad int
Currency [3]byte
}
bigint := new(big.Int)
bigintExpected := big.NewInt(1000000)
bigintExpected2 := big.NewInt(2218516807680)
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
var testCases = []struct {
data string
dest interface{}
expected interface{}
jsonLog []byte
error string
name string
}{{
transferData1,
&EventTransfer{},
&EventTransfer{Value: bigintExpected},
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into structure",
}, {
transferData1,
&[]interface{}{&bigint},
&[]interface{}{&bigintExpected},
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into slice",
}, {
pledgeData1,
&EventPledge{},
&EventPledge{
addr,
bigintExpected2,
[3]byte{'u', 's', 'd'}},
jsonEventPledge,
"",
"Can unpack Pledge event into structure",
}, {
pledgeData1,
&[]interface{}{&common.Address{}, &bigint, &[3]byte{}},
&[]interface{}{
&addr,
&bigintExpected2,
&[3]byte{'u', 's', 'd'}},
jsonEventPledge,
"",
"Can unpack Pledge event into slice",
}, {
pledgeData1,
&[3]interface{}{&common.Address{}, &bigint, &[3]byte{}},
&[3]interface{}{
&addr,
&bigintExpected2,
&[3]byte{'u', 's', 'd'}},
jsonEventPledge,
"",
"Can unpack Pledge event into an array",
}, {
pledgeData1,
&[]interface{}{new(int), 0, 0},
&[]interface{}{},
jsonEventPledge,
"abi: cannot unmarshal common.Address in to int",
"Can not unpack Pledge event into slice with wrong types",
}, {
pledgeData1,
&BadEventPledge{},
&BadEventPledge{},
jsonEventPledge,
"abi: cannot unmarshal common.Address in to string",
"Can not unpack Pledge event into struct with wrong filed types",
}, {
pledgeData1,
&[]interface{}{common.Address{}, new(big.Int)},
&[]interface{}{},
jsonEventPledge,
"abi: insufficient number of elements in the list/array for unpack, want 3, got 2",
"Can not unpack Pledge event into too short slice",
}, {
pledgeData1,
new(map[string]interface{}),
&[]interface{}{},
jsonEventPledge,
"abi: cannot unmarshal tuple into map[string]interface {}",
"Can not unpack Pledge event into map",
}}
for _, tc := range testCases {
assert := assert.New(t)
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
if tc.error == "" {
assert.Nil(err, "Should be able to unpack event data.")
assert.Equal(tc.expected, tc.dest, tc.name)
} else {
assert.EqualError(err, tc.error)
}
})
}
}
func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error {
data, err := hex.DecodeString(hexData)
assert.NoError(err, "Hex data should be a correct hex-string")
var e Event
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
a := ABI{Events: map[string]Event{"e": e}}
return a.Unpack(dest, "e", data)
}
/*
Taken from
https://github.com/EthereumCommonwealth/go-callisto/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 ( import (
"fmt" "fmt"
"reflect"
"strings" "strings"
"github.com/EthereumCommonwealth/go-callisto/crypto" "github.com/EthereumCommonwealth/go-callisto/crypto"
) )
// Callable method given a `Name` and whether the method is a constant. // Method represents a callable given a `Name` and whether the method is a constant.
// If the method is `Const` no transaction needs to be created for this // If the method is `Const` no transaction needs to be created for this
// particular Method call. It can easily be simulated using a local VM. // particular Method call. It can easily be simulated using a local VM.
// For example a `Balance()` method only needs to retrieve something // For example a `Balance()` method only needs to retrieve something
@ -35,125 +34,8 @@ import (
type Method struct { type Method struct {
Name string Name string
Const bool Const bool
Inputs []Argument Inputs Arguments
Outputs []Argument Outputs Arguments
}
func (method Method) pack(args ...interface{}) ([]byte, error) {
// Make sure arguments match up and pack them
if len(args) != len(method.Inputs) {
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(method.Inputs))
}
// variable input is the output appended at the end of packed
// output. This is used for strings and bytes types input.
var variableInput []byte
var ret []byte
for i, a := range args {
input := method.Inputs[i]
// pack the input
packed, err := input.Type.pack(reflect.ValueOf(a))
if err != nil {
return nil, fmt.Errorf("`%s` %v", method.Name, err)
}
// check for a slice type (string, bytes, slice)
if input.Type.requiresLengthPrefix() {
// calculate the offset
offset := len(method.Inputs)*32 + len(variableInput)
// set the offset
ret = append(ret, packNum(reflect.ValueOf(offset))...)
// Append the packed output to the variable input. The variable input
// will be appended at the end of the input.
variableInput = append(variableInput, packed...)
} else {
// append the packed value to the input
ret = append(ret, packed...)
}
}
// append the variable input at the end of the packed input
ret = append(ret, variableInput...)
return ret, nil
}
// unpacks a method return tuple into a struct of corresponding go types
//
// Unpacking can be done into a struct or a slice/array.
func (method Method) tupleUnpack(v interface{}, output []byte) error {
// make sure the passed value is a pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
var (
value = valueOf.Elem()
typ = value.Type()
)
j := 0
for i := 0; i < len(method.Outputs); i++ {
toUnpack := method.Outputs[i]
if toUnpack.Type.T == ArrayTy {
// need to move this up because they read sequentially
j += toUnpack.Type.Size
}
marshalledValue, err := toGoType((i+j)*32, toUnpack.Type, output)
if err != nil {
return err
}
reflectValue := reflect.ValueOf(marshalledValue)
switch value.Kind() {
case reflect.Struct:
for j := 0; j < typ.NumField(); j++ {
field := typ.Field(j)
// TODO read tags: `abi:"fieldName"`
if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] {
if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil {
return err
}
}
}
case reflect.Slice, reflect.Array:
if value.Len() < i {
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(method.Outputs), value.Len())
}
v := value.Index(i)
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
return fmt.Errorf("abi: cannot unmarshal %v in to %v", v.Type(), reflectValue.Type())
}
reflectValue := reflect.ValueOf(marshalledValue)
if err := set(v.Elem(), reflectValue, method.Outputs[i]); err != nil {
return err
}
default:
return fmt.Errorf("abi: cannot unmarshal tuple in to %v", typ)
}
}
return nil
}
func (method Method) isTupleReturn() bool { return len(method.Outputs) > 1 }
func (method Method) singleUnpack(v interface{}, output []byte) error {
// make sure the passed value is a pointer
valueOf := reflect.ValueOf(v)
if reflect.Ptr != valueOf.Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
}
value := valueOf.Elem()
marshalledValue, err := toGoType(0, method.Outputs[0].Type, output)
if err != nil {
return err
}
if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil {
return err
}
return nil
} }
// Sig returns the methods string signature according to the ABI spec. // Sig returns the methods string signature according to the ABI spec.
@ -163,35 +45,35 @@ func (method Method) singleUnpack(v interface{}, output []byte) error {
// function foo(uint32 a, int b) = "foo(uint32,int256)" // function foo(uint32 a, int b) = "foo(uint32,int256)"
// //
// Please note that "int" is substitute for its canonical representation "int256" // Please note that "int" is substitute for its canonical representation "int256"
func (m Method) Sig() string { func (method Method) Sig() string {
types := make([]string, len(m.Inputs)) types := make([]string, len(method.Inputs))
i := 0 i := 0
for _, input := range m.Inputs { for _, input := range method.Inputs {
types[i] = input.Type.String() types[i] = input.Type.String()
i++ i++
} }
return fmt.Sprintf("%v(%v)", m.Name, strings.Join(types, ",")) return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
} }
func (m Method) String() string { func (method Method) String() string {
inputs := make([]string, len(m.Inputs)) inputs := make([]string, len(method.Inputs))
for i, input := range m.Inputs { for i, input := range method.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type) inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
} }
outputs := make([]string, len(m.Outputs)) outputs := make([]string, len(method.Outputs))
for i, output := range m.Outputs { for i, output := range method.Outputs {
if len(output.Name) > 0 { if len(output.Name) > 0 {
outputs[i] = fmt.Sprintf("%v ", output.Name) outputs[i] = fmt.Sprintf("%v ", output.Name)
} }
outputs[i] += output.Type.String() outputs[i] += output.Type.String()
} }
constant := "" constant := ""
if m.Const { if method.Const {
constant = "constant " constant = "constant "
} }
return fmt.Sprintf("function %v(%v) %sreturns(%v)", m.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", ")) return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.Name, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
} }
func (m Method) Id() []byte { func (method Method) Id() []byte {
return crypto.Keccak256([]byte(m.Sig()))[:4] return crypto.Keccak256([]byte(method.Sig()))[:4]
} }

View file

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

View file

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

View file

@ -24,6 +24,7 @@ import (
"strings" "strings"
) )
// Type enumerator
const ( const (
IntTy byte = iota IntTy byte = iota
UintTy UintTy
@ -100,69 +101,66 @@ func NewType(t string) (typ Type, err error) {
return Type{}, fmt.Errorf("invalid formatting of array type") return Type{}, fmt.Errorf("invalid formatting of array type")
} }
return typ, err return typ, err
} else { }
// parse the type and size of the abi-type. // parse the type and size of the abi-type.
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0] parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
// varSize is the size of the variable // varSize is the size of the variable
var varSize int var varSize int
if len(parsedType[3]) > 0 { if len(parsedType[3]) > 0 {
var err error var err error
varSize, err = strconv.Atoi(parsedType[2]) varSize, err = strconv.Atoi(parsedType[2])
if err != nil { if err != nil {
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err) return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
}
} else {
if parsedType[0] == "uint" || parsedType[0] == "int" {
// this should fail because it means that there's something wrong with
// the abi type (the compiler should always format it to the size...always)
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
}
} }
// varType is the parsed abi type } else {
varType := parsedType[1] if parsedType[0] == "uint" || parsedType[0] == "int" {
// this should fail because it means that there's something wrong with
switch varType { // the abi type (the compiler should always format it to the size...always)
case "int":
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
typ.Size = varSize
typ.T = IntTy
case "uint":
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
typ.Size = varSize
typ.T = UintTy
case "bool":
typ.Kind = reflect.Bool
typ.T = BoolTy
typ.Type = reflect.TypeOf(bool(false))
case "address":
typ.Kind = reflect.Array
typ.Type = address_t
typ.Size = 20
typ.T = AddressTy
case "string":
typ.Kind = reflect.String
typ.Type = reflect.TypeOf("")
typ.T = StringTy
case "bytes":
if varSize == 0 {
typ.T = BytesTy
typ.Kind = reflect.Slice
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
} else {
typ.T = FixedBytesTy
typ.Kind = reflect.Array
typ.Size = varSize
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
}
case "function":
typ.Kind = reflect.Array
typ.T = FunctionTy
typ.Size = 24
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
default:
return Type{}, fmt.Errorf("unsupported arg type: %s", t) return Type{}, fmt.Errorf("unsupported arg type: %s", t)
} }
} }
// varType is the parsed abi type
switch varType := parsedType[1]; varType {
case "int":
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
typ.Size = varSize
typ.T = IntTy
case "uint":
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
typ.Size = varSize
typ.T = UintTy
case "bool":
typ.Kind = reflect.Bool
typ.T = BoolTy
typ.Type = reflect.TypeOf(bool(false))
case "address":
typ.Kind = reflect.Array
typ.Type = address_t
typ.Size = 20
typ.T = AddressTy
case "string":
typ.Kind = reflect.String
typ.Type = reflect.TypeOf("")
typ.T = StringTy
case "bytes":
if varSize == 0 {
typ.T = BytesTy
typ.Kind = reflect.Slice
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
} else {
typ.T = FixedBytesTy
typ.Kind = reflect.Array
typ.Size = varSize
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
}
case "function":
typ.Kind = reflect.Array
typ.T = FunctionTy
typ.Size = 24
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
default:
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
}
return return
} }

View file

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

View file

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

View file

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

View file

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

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

View file

@ -21,7 +21,6 @@ package main
import ( import (
"bytes" "bytes"
"compress/zlib"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -698,11 +697,7 @@ func authTwitter(url string) (string, string, common.Address, error) {
} }
defer res.Body.Close() defer res.Body.Close()
reader, err := zlib.NewReader(res.Body) body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", common.Address{}, err
}
body, err := ioutil.ReadAll(reader)
if err != nil { if err != nil {
return "", "", common.Address{}, err return "", "", common.Address{}, err
} }

View file

@ -120,8 +120,12 @@ func remoteConsole(ctx *cli.Context) error {
if ctx.GlobalIsSet(utils.DataDirFlag.Name) { if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
path = ctx.GlobalString(utils.DataDirFlag.Name) path = ctx.GlobalString(utils.DataDirFlag.Name)
} }
if path != "" && ctx.GlobalBool(utils.TestnetFlag.Name) { if path != "" {
path = filepath.Join(path, "testnet") 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) endpoint = fmt.Sprintf("%s/geth.ipc", path)
} }

View file

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

View file

@ -52,3 +52,13 @@ func getFdLimit() (int, error) {
} }
return int(limit.Cur), nil return int(limit.Cur), nil
} }
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
// allowed to request for itself.
func getFdMaxLimit() (int, error) {
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return 0, err
}
return int(limit.Max), nil
}

View file

@ -16,12 +16,22 @@
package utils package utils
import "testing" import (
"fmt"
"testing"
)
// TestFileDescriptorLimits simply tests whether the file descriptor allowance // TestFileDescriptorLimits simply tests whether the file descriptor allowance
// per this process can be retrieved. // per this process can be retrieved.
func TestFileDescriptorLimits(t *testing.T) { func TestFileDescriptorLimits(t *testing.T) {
target := 4096 target := 4096
hardlimit, err := getFdMaxLimit()
if err != nil {
t.Fatal(err)
}
if hardlimit < target {
t.Skip(fmt.Sprintf("system limit is less than desired test target: %d < %d", hardlimit, target))
}
if limit, err := getFdLimit(); err != nil || limit <= 0 { if limit, err := getFdLimit(); err != nil || limit <= 0 {
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err) t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)

View file

@ -48,3 +48,13 @@ func getFdLimit() (int, error) {
} }
return int(limit.Cur), nil return int(limit.Cur), nil
} }
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
// allowed to request for itself.
func getFdMaxLimit() (int, error) {
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return 0, err
}
return int(limit.Max), nil
}

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 // Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
return 16384, nil return 16384, nil
} }
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
// allowed to request for itself.
func getFdMaxLimit() (int, error) {
return getFdLimit()
}

View file

@ -511,7 +511,6 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
header.Nonce = types.BlockNonce{} header.Nonce = types.BlockNonce{}
number := header.Number.Uint64() number := header.Number.Uint64()
// Assemble the voting snapshot to check which votes make sense // Assemble the voting snapshot to check which votes make sense
snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) snap, err := c.snapshot(chain, number-1, header.ParentHash, nil)
if err != nil { if err != nil {
@ -539,10 +538,8 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
c.lock.RUnlock() c.lock.RUnlock()
} }
// Set the correct difficulty // Set the correct difficulty
header.Difficulty = diffNoTurn header.Difficulty = CalcDifficulty(snap, c.signer)
if snap.inturn(header.Number.Uint64(), c.signer) {
header.Difficulty = diffInTurn
}
// Ensure the extra data has all it's components // Ensure the extra data has all it's components
if len(header.Extra) < extraVanity { if len(header.Extra) < extraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...) header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
@ -574,7 +571,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block // Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given, and returns the final block. // rewards given, and returns the final block.
func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
ethash.AccumulateRewards(chain.Config(), state, header, uncles) ethash.InvokeAccumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = types.CalcUncleHash(nil) header.UncleHash = types.CalcUncleHash(nil)
@ -656,6 +653,27 @@ func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-ch
return block.WithSeal(header), nil return block.WithSeal(header), nil
} }
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have based on the previous blocks in the chain and the
// current signer.
func (c *Clique) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
snap, err := c.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
if err != nil {
return nil
}
return CalcDifficulty(snap, c.signer)
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have based on the previous blocks in the chain and the
// current signer.
func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
if snap.inturn(snap.Number+1, signer) {
return new(big.Int).Set(diffInTurn)
}
return new(big.Int).Set(diffNoTurn)
}
// APIs implements consensus.Engine, returning the user facing RPC API to allow // APIs implements consensus.Engine, returning the user facing RPC API to allow
// controlling the signer voting. // controlling the signer voting.
func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API { func (c *Clique) APIs(chain consensus.ChainReader) []rpc.API {

View file

@ -18,6 +18,8 @@
package consensus package consensus
import ( import (
"math/big"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/core/state" "github.com/EthereumCommonwealth/go-callisto/core/state"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
@ -88,6 +90,10 @@ type Engine interface {
// seal place on top. // seal place on top.
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error)
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have.
CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
// APIs returns the RPC APIs this consensus engine provides. // APIs returns the RPC APIs this consensus engine provides.
APIs(chain ChainReader) []rpc.API APIs(chain ChainReader) []rpc.API
} }

View file

@ -242,7 +242,8 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
return errZeroBlockTime return errZeroBlockTime
} }
// Verify the block's difficulty based in it's timestamp and parent's difficulty // Verify the block's difficulty based in it's timestamp and parent's difficulty
expected := CalcDifficulty(chain.Config(), header.Time.Uint64(), parent) expected := ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)
if expected.Cmp(header.Difficulty) != 0 { if expected.Cmp(header.Difficulty) != 0 {
return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected) return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
} }
@ -289,7 +290,13 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *
// CalcDifficulty is the difficulty adjustment algorithm. It returns // CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time // the difficulty that a new block should have when created at time
// given the parent block's time and difficulty. // given the parent block's time and difficulty.
// TODO (karalabe): Move the chain maker into this package and make this private! func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
return CalcDifficulty(chain.Config(), time, parent)
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
next := new(big.Int).Add(parent.Number, big1) next := new(big.Int).Add(parent.Number, big1)
switch { switch {
@ -555,8 +562,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
if parent == nil { if parent == nil {
return consensus.ErrUnknownAncestor return consensus.ErrUnknownAncestor
} }
header.Difficulty = CalcDifficulty(chain.Config(), header.Time.Uint64(), parent) header.Difficulty = ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)
return nil return nil
} }
@ -564,7 +570,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header)
// setting the final state and assembling the block. // setting the final state and assembling the block.
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// Accumulate any block and uncle rewards and commit the final state root // Accumulate any block and uncle rewards and commit the final state root
AccumulateRewards(chain.Config(), state, header, uncles) accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return // Header seems complete, assemble into a block and return
@ -577,11 +583,15 @@ var (
big32 = big.NewInt(32) big32 = big.NewInt(32)
) )
// InvokeAccumulateRewards calls accumulateRewards from Clique for Callisto.
func InvokeAccumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
accumulateRewards(config, state, header, uncles)
}
// AccumulateRewards credits the coinbase of the given block with the mining // AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for // reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded. // included uncles. The coinbase of each uncle block is also rewarded.
// TODO (karalabe): Move the chain maker into this package and make this private! func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
func AccumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression // Select the correct block reward based on chain progression
blockReward := FrontierBlockReward blockReward := FrontierBlockReward

View file

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

View file

@ -92,6 +92,9 @@ func New(config Config) (*Console, error) {
printer: config.Printer, printer: config.Printer,
histPath: filepath.Join(config.DataDir, HistoryFile), histPath: filepath.Join(config.DataDir, HistoryFile),
} }
if err := os.MkdirAll(config.DataDir, 0700); err != nil {
return nil, err
}
if err := console.init(config.Preload); err != nil { if err := console.init(config.Preload); err != nil {
return nil, err return nil, err
} }
@ -423,7 +426,7 @@ func (c *Console) Execute(path string) error {
return c.jsre.Exec(path) return c.jsre.Exec(path)
} }
// Stop cleans up the console and terminates the runtime envorinment. // Stop cleans up the console and terminates the runtime environment.
func (c *Console) Stop(graceful bool) error { func (c *Console) Stop(graceful bool) error {
if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil { if err := ioutil.WriteFile(c.histPath, []byte(strings.Join(c.history, "\n")), 0600); err != nil {
return err return err

View file

@ -137,7 +137,7 @@ func (r *ReleaseService) checkVersion() {
if err != nil { if err != nil {
if err == bind.ErrNoCode { if err == bind.ErrNoCode {
log.Debug("Release oracle not found", "contract", r.config.Oracle) log.Debug("Release oracle not found", "contract", r.config.Oracle)
} else { } else if err != les.ErrNoPeers {
log.Error("Failed to retrieve current release", "err", err) log.Error("Failed to retrieve current release", "err", err)
} }
return return

View file

@ -170,7 +170,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
} }
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
chain, _ := GenerateChain(gspec.Config, genesis, db, b.N, gen) chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, b.N, gen)
// Time the insertion of the new chain. // Time the insertion of the new chain.
// State and blocks are stored in the same DB. // State and blocks are stored in the same DB.

View file

@ -35,7 +35,7 @@ func TestHeaderVerification(t *testing.T) {
testdb, _ = ethdb.NewMemDatabase() testdb, _ = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig} gspec = &Genesis{Config: params.TestChainConfig}
genesis = gspec.MustCommit(testdb) genesis = gspec.MustCommit(testdb)
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil) blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
) )
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
for i, block := range blocks { for i, block := range blocks {
@ -87,7 +87,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
testdb, _ = ethdb.NewMemDatabase() testdb, _ = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig} gspec = &Genesis{Config: params.TestChainConfig}
genesis = gspec.MustCommit(testdb) genesis = gspec.MustCommit(testdb)
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil) blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
) )
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
seals := make([]bool, len(blocks)) seals := make([]bool, len(blocks))
@ -159,7 +159,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
testdb, _ = ethdb.NewMemDatabase() testdb, _ = ethdb.NewMemDatabase()
gspec = &Genesis{Config: params.TestChainConfig} gspec = &Genesis{Config: params.TestChainConfig}
genesis = gspec.MustCommit(testdb) genesis = gspec.MustCommit(testdb)
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 1024, nil) blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil)
) )
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
seals := make([]bool, len(blocks)) seals := make([]bool, len(blocks))

View file

@ -57,7 +57,7 @@ func newTestBlockChain(fake bool) *BlockChain {
// Test fork of length N starting from block i // Test fork of length N starting from block i
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
// Copy old chain up to #i into a new db // Copy old chain up to #i into a new db
db, blockchain2, err := newCanonical(i, full) db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full)
if err != nil { if err != nil {
t.Fatal("could not make new canonical in testFork", err) t.Fatal("could not make new canonical in testFork", err)
} }
@ -81,12 +81,12 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara
headerChainB []*types.Header headerChainB []*types.Header
) )
if full { if full {
blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, db, forkSeed) blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed)
if _, err := blockchain2.InsertChain(blockChainB); err != nil { if _, err := blockchain2.InsertChain(blockChainB); err != nil {
t.Fatalf("failed to insert forking chain: %v", err) t.Fatalf("failed to insert forking chain: %v", err)
} }
} else { } else {
headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, db, forkSeed) headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed)
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil { if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
t.Fatalf("failed to insert forking chain: %v", err) t.Fatalf("failed to insert forking chain: %v", err)
} }
@ -186,7 +186,7 @@ func TestLastBlock(t *testing.T) {
bchain := newTestBlockChain(false) bchain := newTestBlockChain(false)
defer bchain.Stop() defer bchain.Stop()
block := makeBlockChain(bchain.CurrentBlock(), 1, bchain.chainDb, 0)[0] block := makeBlockChain(bchain.CurrentBlock(), 1, ethash.NewFaker(), bchain.chainDb, 0)[0]
bchain.insert(block) bchain.insert(block)
if block.Hash() != GetHeadBlockHash(bchain.chainDb) { if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
t.Errorf("Write/Get HeadBlockHash failed") t.Errorf("Write/Get HeadBlockHash failed")
@ -202,7 +202,7 @@ func testExtendCanonical(t *testing.T, full bool) {
length := 5 length := 5
// Make first chain starting from genesis // Make first chain starting from genesis
_, processor, err := newCanonical(length, full) _, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil { if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err) t.Fatalf("failed to make new canonical chain: %v", err)
} }
@ -230,7 +230,7 @@ func testShorterFork(t *testing.T, full bool) {
length := 10 length := 10
// Make first chain starting from genesis // Make first chain starting from genesis
_, processor, err := newCanonical(length, full) _, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil { if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err) t.Fatalf("failed to make new canonical chain: %v", err)
} }
@ -260,7 +260,7 @@ func testLongerFork(t *testing.T, full bool) {
length := 10 length := 10
// Make first chain starting from genesis // Make first chain starting from genesis
_, processor, err := newCanonical(length, full) _, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil { if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err) t.Fatalf("failed to make new canonical chain: %v", err)
} }
@ -290,7 +290,7 @@ func testEqualFork(t *testing.T, full bool) {
length := 10 length := 10
// Make first chain starting from genesis // Make first chain starting from genesis
_, processor, err := newCanonical(length, full) _, processor, err := newCanonical(ethash.NewFaker(), length, full)
if err != nil { if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err) t.Fatalf("failed to make new canonical chain: %v", err)
} }
@ -317,7 +317,7 @@ func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) }
func testBrokenChain(t *testing.T, full bool) { func testBrokenChain(t *testing.T, full bool) {
// Make chain starting from genesis // Make chain starting from genesis
db, blockchain, err := newCanonical(10, full) db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full)
if err != nil { if err != nil {
t.Fatalf("failed to make new canonical chain: %v", err) t.Fatalf("failed to make new canonical chain: %v", err)
} }
@ -325,12 +325,12 @@ func testBrokenChain(t *testing.T, full bool) {
// Create a forked chain, and try to insert with a missing link // Create a forked chain, and try to insert with a missing link
if full { if full {
chain := makeBlockChain(blockchain.CurrentBlock(), 5, db, forkSeed)[1:] chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed)[1:]
if err := testBlockChainImport(chain, blockchain); err == nil { if err := testBlockChainImport(chain, blockchain); err == nil {
t.Errorf("broken block chain not reported") t.Errorf("broken block chain not reported")
} }
} else { } else {
chain := makeHeaderChain(blockchain.CurrentHeader(), 5, db, forkSeed)[1:] chain := makeHeaderChain(blockchain.CurrentHeader(), 5, ethash.NewFaker(), db, forkSeed)[1:]
if err := testHeaderChainImport(chain, blockchain); err == nil { if err := testHeaderChainImport(chain, blockchain); err == nil {
t.Errorf("broken header chain not reported") t.Errorf("broken header chain not reported")
} }
@ -523,7 +523,7 @@ func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) }
func testInsertNonceError(t *testing.T, full bool) { func testInsertNonceError(t *testing.T, full bool) {
for i := 1; i < 25 && !t.Failed(); i++ { for i := 1; i < 25 && !t.Failed(); i++ {
// Create a pristine chain and database // Create a pristine chain and database
db, blockchain, err := newCanonical(0, full) db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
if err != nil { if err != nil {
t.Fatalf("failed to create pristine chain: %v", err) t.Fatalf("failed to create pristine chain: %v", err)
} }
@ -536,7 +536,7 @@ func testInsertNonceError(t *testing.T, full bool) {
failNum uint64 failNum uint64
) )
if full { if full {
blocks := makeBlockChain(blockchain.CurrentBlock(), i, db, 0) blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0)
failAt = rand.Int() % len(blocks) failAt = rand.Int() % len(blocks)
failNum = blocks[failAt].NumberU64() failNum = blocks[failAt].NumberU64()
@ -544,7 +544,7 @@ func testInsertNonceError(t *testing.T, full bool) {
blockchain.engine = ethash.NewFakeFailer(failNum) blockchain.engine = ethash.NewFakeFailer(failNum)
failRes, err = blockchain.InsertChain(blocks) failRes, err = blockchain.InsertChain(blocks)
} else { } else {
headers := makeHeaderChain(blockchain.CurrentHeader(), i, db, 0) headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0)
failAt = rand.Int() % len(headers) failAt = rand.Int() % len(headers)
failNum = headers[failAt].Number.Uint64() failNum = headers[failAt].Number.Uint64()
@ -588,7 +588,7 @@ func TestFastVsFullChains(t *testing.T) {
genesis = gspec.MustCommit(gendb) genesis = gspec.MustCommit(gendb)
signer = types.NewEIP155Signer(gspec.Config.ChainId) signer = types.NewEIP155Signer(gspec.Config.ChainId)
) )
blocks, receipts := GenerateChain(gspec.Config, genesis, gendb, 1024, func(i int, block *BlockGen) { blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) {
block.SetCoinbase(common.Address{0x00}) block.SetCoinbase(common.Address{0x00})
// If the block number is multiple of 3, send a few bonus transactions to the miner // If the block number is multiple of 3, send a few bonus transactions to the miner
@ -673,7 +673,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
genesis = gspec.MustCommit(gendb) genesis = gspec.MustCommit(gendb)
) )
height := uint64(1024) height := uint64(1024)
blocks, receipts := GenerateChain(gspec.Config, genesis, gendb, int(height), nil) blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), nil)
// Configure a subchain to roll back // Configure a subchain to roll back
remove := []common.Hash{} remove := []common.Hash{}
@ -781,7 +781,7 @@ func TestChainTxReorgs(t *testing.T) {
// - futureAdd: transaction added after the reorg has already finished // - futureAdd: transaction added after the reorg has already finished
var pastAdd, freshAdd, futureAdd *types.Transaction var pastAdd, freshAdd, futureAdd *types.Transaction
chain, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, gen *BlockGen) { chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2) pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
@ -806,7 +806,7 @@ func TestChainTxReorgs(t *testing.T) {
defer blockchain.Stop() defer blockchain.Stop()
// overwrite the old chain // overwrite the old chain
chain, _ = GenerateChain(gspec.Config, genesis, db, 5, func(i int, gen *BlockGen) { chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3) pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
@ -875,7 +875,7 @@ func TestLogReorgs(t *testing.T) {
rmLogsCh := make(chan RemovedLogsEvent) rmLogsCh := make(chan RemovedLogsEvent)
blockchain.SubscribeRemovedLogsEvent(rmLogsCh) blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
chain, _ := GenerateChain(params.TestChainConfig, genesis, db, 2, func(i int, gen *BlockGen) { chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
if i == 1 { if i == 1 {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1) tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
if err != nil { if err != nil {
@ -888,7 +888,7 @@ func TestLogReorgs(t *testing.T) {
t.Fatalf("failed to insert chain: %v", err) t.Fatalf("failed to insert chain: %v", err)
} }
chain, _ = GenerateChain(params.TestChainConfig, genesis, db, 3, func(i int, gen *BlockGen) {}) chain, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
if _, err := blockchain.InsertChain(chain); err != nil { if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert forked chain: %v", err) t.Fatalf("failed to insert forked chain: %v", err)
} }
@ -920,12 +920,12 @@ func TestReorgSideEvent(t *testing.T) {
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{}) blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
defer blockchain.Stop() defer blockchain.Stop()
chain, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, gen *BlockGen) {}) chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
if _, err := blockchain.InsertChain(chain); err != nil { if _, err := blockchain.InsertChain(chain); err != nil {
t.Fatalf("failed to insert chain: %v", err) t.Fatalf("failed to insert chain: %v", err)
} }
replacementBlocks, _ := GenerateChain(gspec.Config, genesis, db, 4, func(i int, gen *BlockGen) { replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) {
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1) tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1)
if i == 2 { if i == 2 {
gen.OffsetTime(-9) gen.OffsetTime(-9)
@ -992,7 +992,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
bc := newTestBlockChain(true) bc := newTestBlockChain(true)
defer bc.Stop() defer bc.Stop()
chain, _ := GenerateChain(bc.config, bc.genesisBlock, bc.chainDb, 10, func(i int, gen *BlockGen) {}) chain, _ := GenerateChain(bc.config, bc.genesisBlock, ethash.NewFaker(), bc.chainDb, 10, func(i int, gen *BlockGen) {})
var pend sync.WaitGroup var pend sync.WaitGroup
pend.Add(len(chain)) pend.Add(len(chain))
@ -1046,7 +1046,7 @@ func TestEIP155Transition(t *testing.T) {
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{}) blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
defer blockchain.Stop() defer blockchain.Stop()
blocks, _ := GenerateChain(gspec.Config, genesis, db, 4, func(i int, block *BlockGen) { blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
var ( var (
tx *types.Transaction tx *types.Transaction
err error err error
@ -1109,7 +1109,7 @@ func TestEIP155Transition(t *testing.T) {
// generate an invalid chain id transaction // generate an invalid chain id transaction
config := &params.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)} 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 ( var (
tx *types.Transaction tx *types.Transaction
err error err error
@ -1154,7 +1154,7 @@ func TestEIP161AccountRemoval(t *testing.T) {
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{}) blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
defer blockchain.Stop() defer blockchain.Stop()
blocks, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, block *BlockGen) { blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, block *BlockGen) {
var ( var (
tx *types.Transaction tx *types.Transaction
err error err error

View file

@ -21,7 +21,7 @@ import (
"math/big" "math/big"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/consensus/ethash" "github.com/EthereumCommonwealth/go-callisto/consensus"
"github.com/EthereumCommonwealth/go-callisto/consensus/misc" "github.com/EthereumCommonwealth/go-callisto/consensus/misc"
"github.com/EthereumCommonwealth/go-callisto/core/state" "github.com/EthereumCommonwealth/go-callisto/core/state"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
@ -39,11 +39,12 @@ var (
// BlockGen creates blocks for testing. // BlockGen creates blocks for testing.
// See GenerateChain for a detailed explanation. // See GenerateChain for a detailed explanation.
type BlockGen struct { type BlockGen struct {
i int i int
parent *types.Block parent *types.Block
chain []*types.Block chain []*types.Block
header *types.Header chainReader consensus.ChainReader
statedb *state.StateDB header *types.Header
statedb *state.StateDB
gasPool *GasPool gasPool *GasPool
txs []*types.Transaction txs []*types.Transaction
@ -51,6 +52,7 @@ type BlockGen struct {
uncles []*types.Header uncles []*types.Header
config *params.ChainConfig config *params.ChainConfig
engine consensus.Engine
} }
// SetCoinbase sets the coinbase of the generated block. // SetCoinbase sets the coinbase of the generated block.
@ -141,7 +143,7 @@ func (b *BlockGen) OffsetTime(seconds int64) {
if b.header.Time.Cmp(b.parent.Header().Time) <= 0 { if b.header.Time.Cmp(b.parent.Header().Time) <= 0 {
panic("block time out of range") panic("block time out of range")
} }
b.header.Difficulty = ethash.CalcDifficulty(b.config, b.header.Time.Uint64(), b.parent.Header()) b.header.Difficulty = b.engine.CalcDifficulty(b.chainReader, b.header.Time.Uint64(), b.parent.Header())
} }
// GenerateChain creates a chain of n blocks. The first block's // GenerateChain creates a chain of n blocks. The first block's
@ -156,44 +158,54 @@ func (b *BlockGen) OffsetTime(seconds int64) {
// Blocks created by GenerateChain do not contain valid proof of work // Blocks created by GenerateChain do not contain valid proof of work
// values. Inserting them into BlockChain requires use of FakePow or // values. Inserting them into BlockChain requires use of FakePow or
// a similar non-validating proof of work implementation. // a similar non-validating proof of work implementation.
func GenerateChain(config *params.ChainConfig, parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) {
if config == nil { if config == nil {
config = params.TestChainConfig config = params.TestChainConfig
} }
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
genblock := func(i int, h *types.Header, statedb *state.StateDB) (*types.Block, types.Receipts) { genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{parent: parent, i: i, chain: blocks, header: h, statedb: statedb, config: config} // TODO(karalabe): This is needed for clique, which depends on multiple blocks.
// It's nonetheless ugly to spin up a blockchain here. Get rid of this somehow.
blockchain, _ := NewBlockChain(db, config, engine, vm.Config{})
defer blockchain.Stop()
b := &BlockGen{i: i, parent: parent, chain: blocks, chainReader: blockchain, statedb: statedb, config: config, engine: engine}
b.header = makeHeader(b.chainReader, parent, statedb, b.engine)
// Mutate the state and block according to any hard-fork specs // Mutate the state and block according to any hard-fork specs
if daoBlock := config.DAOForkBlock; daoBlock != nil { if daoBlock := config.DAOForkBlock; daoBlock != nil {
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
if h.Number.Cmp(daoBlock) >= 0 && h.Number.Cmp(limit) < 0 { if b.header.Number.Cmp(daoBlock) >= 0 && b.header.Number.Cmp(limit) < 0 {
if config.DAOForkSupport { if config.DAOForkSupport {
h.Extra = common.CopyBytes(params.DAOForkBlockExtra) b.header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
} }
} }
} }
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(h.Number) == 0 { if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
misc.ApplyDAOHardFork(statedb) misc.ApplyDAOHardFork(statedb)
} }
// Execute any user modifications to the block and finalize it // Execute any user modifications to the block and finalize it
if gen != nil { if gen != nil {
gen(i, b) gen(i, b)
} }
ethash.AccumulateRewards(config, statedb, h, b.uncles)
root, err := statedb.CommitTo(db, config.IsEIP158(h.Number)) if b.engine != nil {
if err != nil { block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
panic(fmt.Sprintf("state write error: %v", err)) // Write state changes to db
_, err := statedb.CommitTo(db, config.IsEIP158(b.header.Number))
if err != nil {
panic(fmt.Sprintf("state write error: %v", err))
}
return block, b.receipts
} }
h.Root = root return nil, nil
return types.NewBlock(h, b.txs, b.uncles, b.receipts), b.receipts
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
statedb, err := state.New(parent.Root(), state.NewDatabase(db)) statedb, err := state.New(parent.Root(), state.NewDatabase(db))
if err != nil { if err != nil {
panic(err) panic(err)
} }
header := makeHeader(config, parent, statedb) block, receipt := genblock(i, parent, statedb)
block, receipt := genblock(i, header, statedb)
blocks[i] = block blocks[i] = block
receipts[i] = receipt receipts[i] = receipt
parent = block parent = block
@ -201,7 +213,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, db ethdb.Dat
return blocks, receipts return blocks, receipts
} }
func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.StateDB) *types.Header { func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
var time *big.Int var time *big.Int
if parent.Time() == nil { if parent.Time() == nil {
time = big.NewInt(10) time = big.NewInt(10)
@ -210,10 +222,10 @@ func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.St
} }
return &types.Header{ return &types.Header{
Root: state.IntermediateRoot(config.IsEIP158(parent.Number())), Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
ParentHash: parent.Hash(), ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(), Coinbase: parent.Coinbase(),
Difficulty: ethash.CalcDifficulty(config, time.Uint64(), &types.Header{ Difficulty: engine.CalcDifficulty(chain, time.Uint64(), &types.Header{
Number: parent.Number(), Number: parent.Number(),
Time: new(big.Int).Sub(time, big.NewInt(10)), Time: new(big.Int).Sub(time, big.NewInt(10)),
Difficulty: parent.Difficulty(), Difficulty: parent.Difficulty(),
@ -229,32 +241,32 @@ func makeHeader(config *params.ChainConfig, parent *types.Block, state *state.St
// newCanonical creates a chain database, and injects a deterministic canonical // newCanonical creates a chain database, and injects a deterministic canonical
// chain. Depending on the full flag, if creates either a full block chain or a // chain. Depending on the full flag, if creates either a full block chain or a
// header only chain. // header only chain.
func newCanonical(n int, full bool) (ethdb.Database, *BlockChain, error) { func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *BlockChain, error) {
// Initialize a fresh chain with only a genesis block // Initialize a fresh chain with only a genesis block
gspec := new(Genesis) gspec := new(Genesis)
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
blockchain, _ := NewBlockChain(db, params.AllEthashProtocolChanges, ethash.NewFaker(), vm.Config{}) blockchain, _ := NewBlockChain(db, params.AllEthashProtocolChanges, engine, vm.Config{})
// Create and inject the requested chain // Create and inject the requested chain
if n == 0 { if n == 0 {
return db, blockchain, nil return db, blockchain, nil
} }
if full { if full {
// Full block-chain requested // Full block-chain requested
blocks := makeBlockChain(genesis, n, db, canonicalSeed) blocks := makeBlockChain(genesis, n, engine, db, canonicalSeed)
_, err := blockchain.InsertChain(blocks) _, err := blockchain.InsertChain(blocks)
return db, blockchain, err return db, blockchain, err
} }
// Header-only chain requested // Header-only chain requested
headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed) headers := makeHeaderChain(genesis.Header(), n, engine, db, canonicalSeed)
_, err := blockchain.InsertHeaderChain(headers, 1) _, err := blockchain.InsertHeaderChain(headers, 1)
return db, blockchain, err return db, blockchain, err
} }
// makeHeaderChain creates a deterministic chain of headers rooted at parent. // makeHeaderChain creates a deterministic chain of headers rooted at parent.
func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header { func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, db, seed) blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
headers := make([]*types.Header, len(blocks)) headers := make([]*types.Header, len(blocks))
for i, block := range blocks { for i, block := range blocks {
headers[i] = block.Header() headers[i] = block.Header()
@ -263,8 +275,8 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [
} }
// makeBlockChain creates a deterministic chain of blocks rooted at parent. // makeBlockChain creates a deterministic chain of blocks rooted at parent.
func makeBlockChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block { func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Block {
blocks, _ := GenerateChain(params.TestChainConfig, parent, db, n, func(i int, b *BlockGen) { blocks, _ := GenerateChain(params.TestChainConfig, parent, engine, db, n, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)})
}) })
return blocks return blocks

View file

@ -50,7 +50,7 @@ func ExampleGenerateChain() {
// each block and adds different features to gen based on the // each block and adds different features to gen based on the
// block index. // block index.
signer := types.HomesteadSigner{} signer := types.HomesteadSigner{}
chain, _ := GenerateChain(gspec.Config, genesis, db, 5, func(i int, gen *BlockGen) { chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
switch i { switch i {
case 0: case 0:
// In block 1, addr1 sends addr2 some ether. // In block 1, addr1 sends addr2 some ether.

View file

@ -35,7 +35,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
db, _ := ethdb.NewMemDatabase() db, _ := ethdb.NewMemDatabase()
gspec := new(Genesis) gspec := new(Genesis)
genesis := gspec.MustCommit(db) genesis := gspec.MustCommit(db)
prefix, _ := GenerateChain(params.TestChainConfig, genesis, db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {}) prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
// Create the concurrent, conflicting two nodes // Create the concurrent, conflicting two nodes
proDb, _ := ethdb.NewMemDatabase() proDb, _ := ethdb.NewMemDatabase()
@ -79,12 +79,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import contra-fork chain for expansion: %v", err) t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
} }
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
if _, err := conBc.InsertChain(blocks); err == nil { if _, err := conBc.InsertChain(blocks); err == nil {
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0]) t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
} }
// Create a proper no-fork block for the contra-forker // Create a proper no-fork block for the contra-forker
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
if _, err := conBc.InsertChain(blocks); err != nil { if _, err := conBc.InsertChain(blocks); err != nil {
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err) t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
} }
@ -101,12 +101,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import pro-fork chain for expansion: %v", err) t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
} }
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
if _, err := proBc.InsertChain(blocks); err == nil { if _, err := proBc.InsertChain(blocks); err == nil {
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0]) t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
} }
// Create a proper pro-fork block for the pro-forker // Create a proper pro-fork block for the pro-forker
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
if _, err := proBc.InsertChain(blocks); err != nil { if _, err := proBc.InsertChain(blocks); err != nil {
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err) t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
} }
@ -124,7 +124,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import contra-fork chain for expansion: %v", err) t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
} }
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
if _, err := conBc.InsertChain(blocks); err != nil { if _, err := conBc.InsertChain(blocks); err != nil {
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err) t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
} }
@ -141,7 +141,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
if _, err := bc.InsertChain(blocks); err != nil { if _, err := bc.InsertChain(blocks); err != nil {
t.Fatalf("failed to import pro-fork chain for expansion: %v", err) t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
} }
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {}) blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
if _, err := proBc.InsertChain(blocks); err != nil { if _, err := proBc.InsertChain(blocks); err != nil {
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err) t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
} }

View file

@ -103,7 +103,7 @@ var (
underpricedTxCounter = metrics.NewCounter("txpool/underpriced") underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
) )
// TxStatus is the current status of a transaction as seen py the pool. // TxStatus is the current status of a transaction as seen by the pool.
type TxStatus uint type TxStatus uint
const ( const (
@ -199,7 +199,7 @@ type TxPool struct {
pendingState *state.ManagedState // Pending state tracking virtual nonces pendingState *state.ManagedState // Pending state tracking virtual nonces
currentMaxGas *big.Int // Current gas limit for transaction caps currentMaxGas *big.Int // Current gas limit for transaction caps
locals *accountSet // Set of local transaction to exepmt from evicion rules locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk journal *txJournal // Journal of local transaction to back up to disk
pending map[common.Address]*txList // All currently processable transactions pending map[common.Address]*txList // All currently processable transactions
@ -214,7 +214,7 @@ type TxPool struct {
} }
// NewTxPool creates a new transaction pool to gather, sort and filter inbound // NewTxPool creates a new transaction pool to gather, sort and filter inbound
// trnsactions from the network. // transactions from the network.
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool { func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
// Sanitize the input to ensure no vulnerable gas prices are set // Sanitize the input to ensure no vulnerable gas prices are set
config = (&config).sanitize() config = (&config).sanitize()
@ -360,7 +360,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
newNum := newHead.Number.Uint64() newNum := newHead.Number.Uint64()
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
log.Warn("Skipping deep transaction reorg", "depth", depth) log.Debug("Skipping deep transaction reorg", "depth", depth)
} else { } else {
// Reorg seems shallow enough to pull in all transactions into memory // Reorg seems shallow enough to pull in all transactions into memory
var discarded, included types.Transactions var discarded, included types.Transactions
@ -838,7 +838,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
for i, hash := range hashes { for i, hash := range hashes {
if tx := pool.all[hash]; tx != nil { if tx := pool.all[hash]; tx != nil {
from, _ := types.Sender(pool.signer, tx) // already validated from, _ := types.Sender(pool.signer, tx) // already validated
if pool.pending[from].txs.items[tx.Nonce()] != nil { if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
status[i] = TxStatusPending status[i] = TxStatusPending
} else { } else {
status[i] = TxStatusQueued status[i] = TxStatusQueued

View file

@ -1563,6 +1563,63 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
pool.Stop() pool.Stop()
} }
// TestTransactionStatusCheck tests that the pool can correctly retrieve the
// pending status of individual transactions.
func TestTransactionStatusCheck(t *testing.T) {
t.Parallel()
// Create the pool to test the status retrievals with
db, _ := ethdb.NewMemDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
blockchain := &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
defer pool.Stop()
// Create the test accounts to check various transaction statuses with
keys := make([]*ecdsa.PrivateKey, 3)
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
}
// Generate and queue a batch of transactions, both pending and queued
txs := types.Transactions{}
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[0])) // Pending only
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[1])) // Pending and queued
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1]))
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[2])) // Queued only
// Import the transaction and ensure they are correctly added
pool.AddRemotes(txs)
pending, queued := pool.Stats()
if pending != 2 {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
}
if queued != 2 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
}
if err := validateTxPoolInternals(pool); err != nil {
t.Fatalf("pool internal state corrupted: %v", err)
}
// Retrieve the status of each transaction and validate them
hashes := make([]common.Hash, len(txs))
for i, tx := range txs {
hashes[i] = tx.Hash()
}
hashes = append(hashes, common.Hash{})
statuses := pool.Status(hashes)
expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown}
for i := 0; i < len(statuses); i++ {
if statuses[i] != expect[i] {
t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i])
}
}
}
// Benchmarks the speed of validating the contents of the pending queue of the // Benchmarks the speed of validating the contents of the pending queue of the
// transaction pool. // transaction pool.
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }

View file

@ -153,6 +153,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
return err return err
} }
// MarshalJSON encodes the web3 RPC transaction format.
func (tx *Transaction) MarshalJSON() ([]byte, error) { func (tx *Transaction) MarshalJSON() ([]byte, error) {
hash := tx.Hash() hash := tx.Hash()
data := tx.data data := tx.data
@ -168,8 +169,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
} }
var V byte var V byte
if isProtectedV(dec.V) { if isProtectedV(dec.V) {
chainId := deriveChainId(dec.V).Uint64() chainID := deriveChainId(dec.V).Uint64()
V = byte(dec.V.Uint64() - 35 - 2*chainId) V = byte(dec.V.Uint64() - 35 - 2*chainID)
} else { } else {
V = byte(dec.V.Uint64() - 27) V = byte(dec.V.Uint64() - 27)
} }
@ -192,10 +193,9 @@ func (tx *Transaction) CheckNonce() bool { return true }
func (tx *Transaction) To() *common.Address { func (tx *Transaction) To() *common.Address {
if tx.data.Recipient == nil { if tx.data.Recipient == nil {
return nil return nil
} else {
to := *tx.data.Recipient
return &to
} }
to := *tx.data.Recipient
return &to
} }
// Hash hashes the RLP encoding of tx. // Hash hashes the RLP encoding of tx.
@ -315,22 +315,22 @@ func (tx *Transaction) String() string {
) )
} }
// Transaction slice type for basic sorting. // Transactions is a Transaction slice type for basic sorting.
type Transactions []*Transaction type Transactions []*Transaction
// Len returns the length of s // Len returns the length of s.
func (s Transactions) Len() int { return len(s) } func (s Transactions) Len() int { return len(s) }
// Swap swaps the i'th and the j'th element in s // Swap swaps the i'th and the j'th element in s.
func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// GetRlp implements Rlpable and returns the i'th element of s in rlp // GetRlp implements Rlpable and returns the i'th element of s in rlp.
func (s Transactions) GetRlp(i int) []byte { func (s Transactions) GetRlp(i int) []byte {
enc, _ := rlp.EncodeToBytes(s[i]) enc, _ := rlp.EncodeToBytes(s[i])
return enc return enc
} }
// Returns a new set t which is the difference between a to b // TxDifference returns a new set t which is the difference between a to b.
func TxDifference(a, b Transactions) (keep Transactions) { func TxDifference(a, b Transactions) (keep Transactions) {
keep = make(Transactions, 0, len(a)) keep = make(Transactions, 0, len(a))
@ -378,7 +378,7 @@ func (s *TxByPrice) Pop() interface{} {
} }
// TransactionsByPriceAndNonce represents a set of transactions that can return // TransactionsByPriceAndNonce represents a set of transactions that can return
// transactions in a profit-maximising sorted order, while supporting removing // transactions in a profit-maximizing sorted order, while supporting removing
// entire batches of transactions for non-executable accounts. // entire batches of transactions for non-executable accounts.
type TransactionsByPriceAndNonce struct { type TransactionsByPriceAndNonce struct {
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions txs map[common.Address]Transactions // Per account nonce-sorted list of transactions

View file

@ -19,6 +19,7 @@ package vm
import ( import (
"math/big" "math/big"
"sync/atomic" "sync/atomic"
"time"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/crypto" "github.com/EthereumCommonwealth/go-callisto/crypto"
@ -38,7 +39,7 @@ type (
) )
// run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter.
func run(evm *EVM, snapshot int, contract *Contract, input []byte) ([]byte, error) { func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) {
if contract.CodeAddr != nil { if contract.CodeAddr != nil {
precompiles := PrecompiledContractsHomestead precompiles := PrecompiledContractsHomestead
if evm.ChainConfig().IsByzantium(evm.BlockNumber) { if evm.ChainConfig().IsByzantium(evm.BlockNumber) {
@ -48,7 +49,7 @@ func run(evm *EVM, snapshot int, contract *Contract, input []byte) ([]byte, erro
return RunPrecompiledContract(p, input, contract) return RunPrecompiledContract(p, input, contract)
} }
} }
return evm.interpreter.Run(snapshot, contract, input) return evm.interpreter.Run(contract, input)
} }
// Context provides the EVM with auxiliary information. Once provided // Context provides the EVM with auxiliary information. Once provided
@ -165,13 +166,23 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
} }
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// initialise a new contract and set the code that is to be used by the // Initialise a new contract and set the code that is to be used by the EVM.
// E The contract is a scoped environment for this execution context // The contract is a scoped environment for this execution context only.
// only.
contract := NewContract(caller, to, value, gas) contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
ret, err = run(evm, snapshot, contract, input) start := time.Now()
// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
defer func() { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}()
}
ret, err = run(evm, contract, input)
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors. // when we're in homestead this also counts for code storage gas errors.
@ -215,7 +226,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
contract := NewContract(caller, to, value, gas) contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
ret, err = run(evm, snapshot, contract, input) ret, err = run(evm, contract, input)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != errExecutionReverted {
@ -248,7 +259,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
contract := NewContract(caller, to, nil, gas).AsDelegate() contract := NewContract(caller, to, nil, gas).AsDelegate()
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
ret, err = run(evm, snapshot, contract, input) ret, err = run(evm, contract, input)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != errExecutionReverted {
@ -291,7 +302,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
// When an error was returned by the EVM or when setting the creation code // When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally // above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in Homestead this also counts for code storage gas errors. // when we're in Homestead this also counts for code storage gas errors.
ret, err = run(evm, snapshot, contract, input) ret, err = run(evm, contract, input)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted { if err != errExecutionReverted {
@ -338,7 +349,14 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.vmConfig.NoRecursion && evm.depth > 0 {
return nil, contractAddr, gas, nil return nil, contractAddr, gas, nil
} }
ret, err = run(evm, snapshot, contract, nil)
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)
}
start := time.Now()
ret, err = run(evm, contract, nil)
// check whether the max code size has been exceeded // check whether the max code size has been exceeded
maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize
// if the contract creation ran successfully and no errors were returned // if the contract creation ran successfully and no errors were returned
@ -367,6 +385,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
if maxCodeSizeExceeded && err == nil { if maxCodeSizeExceeded && err == nil {
err = errMaxCodeSizeExceeded err = errMaxCodeSizeExceeded
} }
if evm.vmConfig.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
}
return ret, contractAddr, contract.Gas, err return ret, contractAddr, contract.Gas, err
} }

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. // the return byte-slice and an error if one occurred.
// //
// It's important to note that any errors returned by the interpreter should be // It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation. No error specific checks // considered a revert-and-consume-all-gas operation except for
// should be handled to reduce complexity and errors further down the in. // errExecutionReverted which means revert-and-keep-gas-left.
func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret []byte, err error) { func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024 // Increment the call depth which is restricted to 1024
in.evm.depth++ in.evm.depth++
defer func() { in.evm.depth-- }() defer func() { in.evm.depth-- }()
@ -144,12 +144,17 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
) )
contract.Input = input contract.Input = input
defer func() { if in.cfg.Debug {
if err != nil && !logged && in.cfg.Debug { defer func() {
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err) if err != nil {
} if !logged {
}() in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
} else {
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
}
}
}()
}
// The Interpreter main run loop (contextual). This loop runs until either an // The Interpreter main run loop (contextual). This loop runs until either an
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
// the execution of one of the operations or until the done flag is set by the // the execution of one of the operations or until the done flag is set by the

View file

@ -84,7 +84,9 @@ func (s *StructLog) OpName() string {
// Note that reference types are actual VM data structures; make copies // Note that reference types are actual VM data structures; make copies
// if you need to retain them beyond the current call. // if you need to retain them beyond the current call.
type Tracer interface { type Tracer interface {
CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) error
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
} }
@ -111,6 +113,10 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
return logger return logger
} }
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
return nil
}
// CaptureState logs a new structured log message and pushes it out to the environment // CaptureState logs a new structured log message and pushes it out to the environment
// //
// CaptureState also tracks SSTORE ops to track dirty values. // CaptureState also tracks SSTORE ops to track dirty values.
@ -161,6 +167,10 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
return nil return nil
} }
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
return nil
}
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error { func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
fmt.Printf("0x%x", output) fmt.Printf("0x%x", output)
if err != nil { if err != nil {

View file

@ -34,7 +34,6 @@ package secp256k1
import ( import (
"crypto/elliptic" "crypto/elliptic"
"math/big" "math/big"
"sync"
"unsafe" "unsafe"
"github.com/EthereumCommonwealth/go-callisto/common/math" "github.com/EthereumCommonwealth/go-callisto/common/math"
@ -42,7 +41,7 @@ import (
/* /*
#include "libsecp256k1/include/secp256k1.h" #include "libsecp256k1/include/secp256k1.h"
extern int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar); extern int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, const unsigned char *point, const unsigned char *scalar);
*/ */
import "C" import "C"
@ -236,7 +235,7 @@ func (BitCurve *BitCurve) ScalarMult(Bx, By *big.Int, scalar []byte) (*big.Int,
math.ReadBits(By, point[32:]) math.ReadBits(By, point[32:])
pointPtr := (*C.uchar)(unsafe.Pointer(&point[0])) pointPtr := (*C.uchar)(unsafe.Pointer(&point[0]))
scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0])) scalarPtr := (*C.uchar)(unsafe.Pointer(&scalar[0]))
res := C.secp256k1_pubkey_scalar_mul(context, pointPtr, scalarPtr) res := C.secp256k1_ext_scalar_mul(context, pointPtr, scalarPtr)
// Unpack the result and clear temporaries. // Unpack the result and clear temporaries.
x := new(big.Int).SetBytes(point[:32]) x := new(big.Int).SetBytes(point[:32])
@ -263,14 +262,10 @@ func (BitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
// X9.62. // X9.62.
func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte { func (BitCurve *BitCurve) Marshal(x, y *big.Int) []byte {
byteLen := (BitCurve.BitSize + 7) >> 3 byteLen := (BitCurve.BitSize + 7) >> 3
ret := make([]byte, 1+2*byteLen) ret := make([]byte, 1+2*byteLen)
ret[0] = 4 // uncompressed point ret[0] = 4 // uncompressed point flag
math.ReadBits(x, ret[1:1+byteLen])
xBytes := x.Bytes() math.ReadBits(y, ret[1+byteLen:])
copy(ret[1+byteLen-len(xBytes):], xBytes)
yBytes := y.Bytes()
copy(ret[1+2*byteLen-len(yBytes):], yBytes)
return ret return ret
} }
@ -289,24 +284,21 @@ func (BitCurve *BitCurve) Unmarshal(data []byte) (x, y *big.Int) {
return return
} }
var ( var theCurve = new(BitCurve)
initonce sync.Once
theCurve *BitCurve
)
// S256 returns a BitCurve which implements secp256k1 (see SEC 2 section 2.7.1) func init() {
// See SEC 2 section 2.7.1
// curve parameters taken from:
// http://www.secg.org/collateral/sec2_final.pdf
theCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
theCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
theCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16)
theCurve.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
theCurve.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
theCurve.BitSize = 256
}
// S256 returns a BitCurve which implements secp256k1.
func S256() *BitCurve { func S256() *BitCurve {
initonce.Do(func() {
// See SEC 2 section 2.7.1
// curve parameters taken from:
// http://www.secg.org/collateral/sec2_final.pdf
theCurve = new(BitCurve)
theCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
theCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
theCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16)
theCurve.Gx, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
theCurve.Gy, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
theCurve.BitSize = 256
})
return theCurve return theCurve
} }

View file

@ -19,7 +19,7 @@ static secp256k1_context* secp256k1_context_create_sign_verify() {
return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); return secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
} }
// secp256k1_ecdsa_recover_pubkey recovers the public key of an encoded compact signature. // secp256k1_ext_ecdsa_recover recovers the public key of an encoded compact signature.
// //
// Returns: 1: recovery was successful // Returns: 1: recovery was successful
// 0: recovery was not successful // 0: recovery was not successful
@ -27,7 +27,7 @@ static secp256k1_context* secp256k1_context_create_sign_verify() {
// Out: pubkey_out: the serialized 65-byte public key of the signer (cannot be NULL) // Out: pubkey_out: the serialized 65-byte public key of the signer (cannot be NULL)
// In: sigdata: pointer to a 65-byte signature with the recovery id at the end (cannot be NULL) // In: sigdata: pointer to a 65-byte signature with the recovery id at the end (cannot be NULL)
// msgdata: pointer to a 32-byte message (cannot be NULL) // msgdata: pointer to a 32-byte message (cannot be NULL)
static int secp256k1_ecdsa_recover_pubkey( static int secp256k1_ext_ecdsa_recover(
const secp256k1_context* ctx, const secp256k1_context* ctx,
unsigned char *pubkey_out, unsigned char *pubkey_out,
const unsigned char *sigdata, const unsigned char *sigdata,
@ -46,7 +46,7 @@ static int secp256k1_ecdsa_recover_pubkey(
return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED); return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED);
} }
// secp256k1_ecdsa_verify_enc verifies an encoded compact signature. // secp256k1_ext_ecdsa_verify verifies an encoded compact signature.
// //
// Returns: 1: signature is valid // Returns: 1: signature is valid
// 0: signature is invalid // 0: signature is invalid
@ -55,7 +55,7 @@ static int secp256k1_ecdsa_recover_pubkey(
// msgdata: pointer to a 32-byte message (cannot be NULL) // msgdata: pointer to a 32-byte message (cannot be NULL)
// pubkeydata: pointer to public key data (cannot be NULL) // pubkeydata: pointer to public key data (cannot be NULL)
// pubkeylen: length of pubkeydata // pubkeylen: length of pubkeydata
static int secp256k1_ecdsa_verify_enc( static int secp256k1_ext_ecdsa_verify(
const secp256k1_context* ctx, const secp256k1_context* ctx,
const unsigned char *sigdata, const unsigned char *sigdata,
const unsigned char *msgdata, const unsigned char *msgdata,
@ -74,28 +74,34 @@ static int secp256k1_ecdsa_verify_enc(
return secp256k1_ecdsa_verify(ctx, &sig, msgdata, &pubkey); return secp256k1_ecdsa_verify(ctx, &sig, msgdata, &pubkey);
} }
// secp256k1_decompress_pubkey decompresses a public key. // 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: public key is valid // Returns: 1: conversion successful
// 0: public key is invalid // 0: conversion unsuccessful
// Args: ctx: pointer to a context object (cannot be NULL) // Args: ctx: pointer to a context object (cannot be NULL)
// Out: pubkey_out: the serialized 65-byte public key (cannot be NULL) // Out: out: output buffer that will contain the reencoded key (cannot be NULL)
// In: pubkeydata: pointer to 33 bytes of compressed public key data (cannot be NULL) // In: outlen: length of out (33 for compressed keys, 65 for uncompressed keys)
static int secp256k1_decompress_pubkey( // pubkeydata: the input public key (cannot be NULL)
// pubkeylen: length of pubkeydata
static int secp256k1_ext_reencode_pubkey(
const secp256k1_context* ctx, const secp256k1_context* ctx,
unsigned char *pubkey_out, unsigned char *out,
const unsigned char *pubkeydata size_t outlen,
const unsigned char *pubkeydata,
size_t pubkeylen
) { ) {
secp256k1_pubkey pubkey; secp256k1_pubkey pubkey;
if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, 33)) { if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubkeydata, pubkeylen)) {
return 0; return 0;
} }
size_t outputlen = 65; unsigned int flag = (outlen == 33) ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED;
return secp256k1_ec_pubkey_serialize(ctx, pubkey_out, &outputlen, &pubkey, SECP256K1_EC_UNCOMPRESSED); return secp256k1_ec_pubkey_serialize(ctx, out, &outlen, &pubkey, flag);
} }
// secp256k1_pubkey_scalar_mul multiplies a point by a scalar in constant time. // secp256k1_ext_scalar_mul multiplies a point by a scalar in constant time.
// //
// Returns: 1: multiplication was successful // Returns: 1: multiplication was successful
// 0: scalar was invalid (zero or overflow) // 0: scalar was invalid (zero or overflow)
@ -104,7 +110,7 @@ static int secp256k1_decompress_pubkey(
// In: point: pointer to a 64-byte public point, // In: point: pointer to a 64-byte public point,
// encoded as two 256bit big-endian numbers. // encoded as two 256bit big-endian numbers.
// scalar: a 32-byte scalar with which to multiply the point // scalar: a 32-byte scalar with which to multiply the point
int secp256k1_pubkey_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) { int secp256k1_ext_scalar_mul(const secp256k1_context* ctx, unsigned char *point, const unsigned char *scalar) {
int ret = 0; int ret = 0;
int overflow = 0; int overflow = 0;
secp256k1_fe feX, feY; secp256k1_fe feX, feY;

View file

@ -115,7 +115,7 @@ func RecoverPubkey(msg []byte, sig []byte) ([]byte, error) {
sigdata = (*C.uchar)(unsafe.Pointer(&sig[0])) sigdata = (*C.uchar)(unsafe.Pointer(&sig[0]))
msgdata = (*C.uchar)(unsafe.Pointer(&msg[0])) msgdata = (*C.uchar)(unsafe.Pointer(&msg[0]))
) )
if C.secp256k1_ecdsa_recover_pubkey(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 { if C.secp256k1_ext_ecdsa_recover(context, (*C.uchar)(unsafe.Pointer(&pubkey[0])), sigdata, msgdata) == 0 {
return nil, ErrRecoverFailed return nil, ErrRecoverFailed
} }
return pubkey, nil return pubkey, nil
@ -130,22 +130,42 @@ func VerifySignature(pubkey, msg, signature []byte) bool {
sigdata := (*C.uchar)(unsafe.Pointer(&signature[0])) sigdata := (*C.uchar)(unsafe.Pointer(&signature[0]))
msgdata := (*C.uchar)(unsafe.Pointer(&msg[0])) msgdata := (*C.uchar)(unsafe.Pointer(&msg[0]))
keydata := (*C.uchar)(unsafe.Pointer(&pubkey[0])) keydata := (*C.uchar)(unsafe.Pointer(&pubkey[0]))
return C.secp256k1_ecdsa_verify_enc(context, sigdata, msgdata, keydata, C.size_t(len(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. // DecompressPubkey parses a public key in the 33-byte compressed format.
// It returns non-nil coordinates if the public key is valid. // It returns non-nil coordinates if the public key is valid.
func DecompressPubkey(pubkey []byte) (X, Y *big.Int) { func DecompressPubkey(pubkey []byte) (x, y *big.Int) {
if len(pubkey) != 33 { if len(pubkey) != 33 {
return nil, nil return nil, nil
} }
buf := make([]byte, 65) var (
bufdata := (*C.uchar)(unsafe.Pointer(&buf[0])) pubkeydata = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
pubkeydata := (*C.uchar)(unsafe.Pointer(&pubkey[0])) pubkeylen = C.size_t(len(pubkey))
if C.secp256k1_decompress_pubkey(context, bufdata, pubkeydata) == 0 { 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 nil, nil
} }
return new(big.Int).SetBytes(buf[1:33]), new(big.Int).SetBytes(buf[33:]) return new(big.Int).SetBytes(out[1:33]), new(big.Int).SetBytes(out[33:])
}
// CompressPubkey encodes a public key to 33-byte compressed format.
func CompressPubkey(x, y *big.Int) []byte {
var (
pubkey = S256().Marshal(x, y)
pubkeydata = (*C.uchar)(unsafe.Pointer(&pubkey[0]))
pubkeylen = C.size_t(len(pubkey))
out = make([]byte, 33)
outdata = (*C.uchar)(unsafe.Pointer(&out[0]))
outlen = C.size_t(len(out))
)
if C.secp256k1_ext_reencode_pubkey(context, outdata, outlen, pubkeydata, pubkeylen) == 0 {
panic("libsecp256k1 error")
}
return out
} }
func checkSignature(sig []byte) error { func checkSignature(sig []byte) error {

View file

@ -76,6 +76,11 @@ func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil return &ecdsa.PublicKey{X: x, Y: y, Curve: S256()}, nil
} }
// CompressPubkey encodes a public key to the 33-byte compressed format.
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
return secp256k1.CompressPubkey(pubkey.X, pubkey.Y)
}
// S256 returns an instance of the secp256k1 curve. // S256 returns an instance of the secp256k1 curve.
func S256() elliptic.Curve { func S256() elliptic.Curve {
return secp256k1.S256() return secp256k1.S256()

View file

@ -87,6 +87,10 @@ func VerifySignature(pubkey, hash, signature []byte) bool {
if err != nil { if err != nil {
return false 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) return sig.Verify(hash, key)
} }
@ -102,6 +106,11 @@ func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) {
return key.ToECDSA(), nil return key.ToECDSA(), nil
} }
// CompressPubkey encodes a public key to the 33-byte compressed format.
func CompressPubkey(pubkey *ecdsa.PublicKey) []byte {
return (*btcec.PublicKey)(pubkey).SerializeCompressed()
}
// S256 returns an instance of the secp256k1 curve. // S256 returns an instance of the secp256k1 curve.
func S256() elliptic.Curve { func S256() elliptic.Curve {
return btcec.S256() return btcec.S256()

View file

@ -18,10 +18,13 @@ package crypto
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"reflect"
"testing" "testing"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/common/hexutil" "github.com/EthereumCommonwealth/go-callisto/common/hexutil"
"github.com/EthereumCommonwealth/go-callisto/common/math"
) )
var ( var (
@ -65,6 +68,21 @@ func TestVerifySignature(t *testing.T) {
if VerifySignature(testpubkey, testmsg, sig[:len(sig)-2]) { if VerifySignature(testpubkey, testmsg, sig[:len(sig)-2]) {
t.Errorf("signature valid even though it's incomplete") 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) { func TestDecompressPubkey(t *testing.T) {
@ -86,6 +104,36 @@ func TestDecompressPubkey(t *testing.T) {
} }
} }
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) { func BenchmarkEcrecoverSignature(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if _, err := Ecrecover(testmsg, testsig); err != nil { if _, err := Ecrecover(testmsg, testsig); err != nil {

View file

@ -9,10 +9,11 @@ The client's UI uses [React][React] with JSX syntax, which is validated by the [
### Development and bundling ### Development and bundling
As the dashboard depends on certain NPM packages (which are not included in the go-ethereum repo), these need to be installed first: As the dashboard depends on certain NPM packages (which are not included in the `go-ethereum` repo), these need to be installed first:
``` ```
$ (cd dashboard/assets && npm install) $ (cd dashboard/assets && npm install)
$ (cd dashboard/assets && ./node_modules/.bin/flow-typed install)
``` ```
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources: Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources:
@ -22,13 +23,20 @@ $ (cd dashboard/assets && ./node_modules/.bin/webpack --watch)
$ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5 $ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5
``` ```
To bundle up the final UI into Geth, run `webpack` and `go generate`: To bundle up the final UI into Geth, run `go generate`:
``` ```
$ (cd dashboard/assets && ./node_modules/.bin/webpack)
$ go generate ./dashboard $ go generate ./dashboard
``` ```
### Static type checking
Since JavaScript doesn't provide type safety, [Flow][Flow] is used to check types. These are only useful during development, so at the end of the process Babel will strip them.
To take advantage of static type checking, your IDE needs to be prepared for it. In case of [Atom][Atom] a configuration guide can be found [here][Atom config]: Install the [Nuclide][Nuclide] package for Flow support, making sure it installs all of its support packages by enabling `Install Recommended Packages on Startup`, and set the path of the `flow-bin` which were installed previously by `npm`.
For more IDE support install the `linter-eslint` package too, which finds the `.eslintrc` file, and provides real-time linting. Atom warns, that these two packages are incompatible, but they seem to work well together. For third-party library errors and auto-completion [flow-typed][flow-typed] is used.
### Have fun ### Have fun
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage. [Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
@ -44,3 +52,8 @@ $ go generate ./dashboard
[WA]: http://webpack.github.io/analyse/ [WA]: http://webpack.github.io/analyse/
[WV]: http://chrisbateman.github.io/webpack-visualizer/ [WV]: http://chrisbateman.github.io/webpack-visualizer/
[Node.js]: https://nodejs.org/en/ [Node.js]: https://nodejs.org/en/
[Flow]: https://flow.org/
[Atom]: https://atom.io/
[Atom config]: https://medium.com/@fastphrase/integrating-flow-into-a-react-project-fbbc2f130eed
[Nuclide]: https://nuclide.io/docs/quick-start/getting-started/
[flow-typed]: https://github.com/flowtype/flow-typed

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 // React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
{ {
"plugins": [ 'env': {
"react" 'browser': true,
], 'node': true,
"parser": "babel-eslint", 'es6': true,
"parserOptions": { },
"ecmaFeatures": { 'parser': 'babel-eslint',
"jsx": true, 'parserOptions': {
"modules": true 'sourceType': 'module',
} 'ecmaVersion': 6,
}, 'ecmaFeatures': {
"rules": { 'jsx': true,
"react/prefer-es6-class": 2, }
"react/prefer-stateless-function": 2, },
"react/jsx-pascal-case": 2, 'extends': 'airbnb',
"react/jsx-closing-bracket-location": [1, {"selfClosing": "tag-aligned", "nonEmpty": "tag-aligned"}], 'plugins': [
"react/jsx-closing-tag-location": 1, 'flowtype',
"jsx-quotes": ["error", "prefer-double"], 'react',
"no-multi-spaces": "error", ],
"react/jsx-tag-spacing": 2, 'rules': {
"react/jsx-curly-spacing": [2, {"when": "never", "children": true}], 'no-tabs': 'off',
"react/jsx-boolean-value": 2, 'indent': ['error', 'tab'],
"react/no-string-refs": 2, 'react/jsx-indent': ['error', 'tab'],
"react/jsx-wrap-multilines": 2, 'react/jsx-indent-props': ['error', 'tab'],
"react/self-closing-comp": 2, 'react/prefer-stateless-function': 'off',
"react/jsx-no-bind": 2,
"react/require-render-return": 2, // Specifies the maximum length of a line.
"react/no-is-mounted": 2, 'max-len': ['warn', 120, 2, {
"key-spacing": ["error", {"align": { 'ignoreUrls': true,
"beforeColon": false, 'ignoreComments': false,
"afterColon": true, 'ignoreRegExpLiterals': true,
"on": "value" 'ignoreStrings': true,
}}] 'ignoreTemplateLiterals': true,
} }],
// Enforces spacing between keys and values in object literal properties.
'key-spacing': ['error', {'align': {
'beforeColon': false,
'afterColon': true,
'on': 'value'
}}],
// Prohibits padding inside curly braces.
'object-curly-spacing': ['error', 'never'],
'no-use-before-define': 'off', // messageAPI
'default-case': 'off',
'flowtype/boolean-style': ['error', 'boolean'],
'flowtype/define-flow-type': 'warn',
'flowtype/generic-spacing': ['error', 'never'],
'flowtype/no-primitive-constructor-types': 'error',
'flowtype/no-weak-types': 'error',
'flowtype/object-type-delimiter': ['error', 'comma'],
'flowtype/require-valid-file-annotation': 'error',
'flowtype/semi': ['error', 'always'],
'flowtype/space-after-type-colon': ['error', 'always'],
'flowtype/space-before-generic-bracket': ['error', 'never'],
'flowtype/space-before-type-colon': ['error', 'never'],
'flowtype/union-intersection-spacing': ['error', 'always'],
'flowtype/use-flow-type': 'warn',
'flowtype/valid-syntax': 'warn',
},
'settings': {
'flowtype': {
'onlyFilesWithFlowAnnotation': true,
}
},
} }

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 // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -14,39 +16,78 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// isNullOrUndefined returns true if the given variable is null or undefined. type ProvidedMenuProp = {|title: string, icon: string|};
export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined'; const menuSkeletons: Array<{|id: string, menu: ProvidedMenuProp|}> = [
{
export const LIMIT = { id: 'home',
memory: 200, // Maximum number of memory data samples. menu: {
traffic: 200, // Maximum number of traffic data samples. title: 'Home',
log: 200, // Maximum number of logs. icon: 'home',
}; },
}, {
id: 'chain',
menu: {
title: 'Chain',
icon: 'link',
},
}, {
id: 'txpool',
menu: {
title: 'TxPool',
icon: 'credit-card',
},
}, {
id: 'network',
menu: {
title: 'Network',
icon: 'globe',
},
}, {
id: 'system',
menu: {
title: 'System',
icon: 'tachometer',
},
}, {
id: 'logs',
menu: {
title: 'Logs',
icon: 'list',
},
},
];
export type MenuProp = {|...ProvidedMenuProp, id: string|};
// The sidebar menu and the main content are rendered based on these elements. // The sidebar menu and the main content are rendered based on these elements.
export const TAGS = (() => { // Using the id is circumstantial in some cases, so it is better to insert it also as a value.
const T = { // This way the mistyping is prevented.
home: { title: "Home", }, export const MENU: Map<string, {...MenuProp}> = new Map(menuSkeletons.map(({id, menu}) => ([id, {id, ...menu}])));
chain: { title: "Chain", },
transactions: { title: "Transactions", },
network: { title: "Network", },
system: { title: "System", },
logs: { title: "Logs", },
};
// Using the key is circumstantial in some cases, so it is better to insert it also as a value.
// This way the mistyping is prevented.
for(let key in T) {
T[key]['id'] = key;
}
return T;
})();
export const DATA_KEYS = (() => { type ProvidedSampleProp = {|limit: number|};
const DK = {}; const sampleSkeletons: Array<{|id: string, sample: ProvidedSampleProp|}> = [
["memory", "traffic", "logs"].map(key => { {
DK[key] = key; id: 'memory',
}); sample: {
return DK; limit: 200,
})(); },
}, {
id: 'traffic',
sample: {
limit: 200,
},
}, {
id: 'logs',
sample: {
limit: 200,
},
},
];
export type SampleProp = {|...ProvidedSampleProp, id: string|};
export const SAMPLE: Map<string, {...SampleProp}> = new Map(sampleSkeletons.map(({id, sample}) => ([id, {id, ...sample}])));
// Temporary - taken from Material-UI export const DURATION = 200;
export const DRAWER_WIDTH = 240;
export const LENS: Map<string, string> = new Map([
'content',
...menuSkeletons.map(({id}) => id),
...sampleSkeletons.map(({id}) => id),
].map(lens => [lens, lens]));

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -15,155 +17,183 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withStyles} from 'material-ui/styles';
import SideBar from './SideBar.jsx'; import withStyles from 'material-ui/styles/withStyles';
import Header from './Header.jsx'; import {lensPath, view, set} from 'ramda';
import Main from "./Main.jsx";
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
// Styles for the Dashboard component. import Header from './Header';
import Body from './Body';
import {MENU, SAMPLE} from './Common';
import type {Message, HomeMessage, LogsMessage, Chart} from '../types/message';
import type {Content} from '../types/content';
// appender appends an array (A) to the end of another array (B) in the state.
// lens is the path of B in the state, samples is A, and limit is the maximum size of the changed array.
//
// appender retrieves a function, which overrides the state's value at lens, and returns with the overridden state.
const appender = (lens, samples, limit) => (state) => {
const newSamples = [
...view(lens, state), // retrieves a specific value of the state at the given path (lens).
...samples,
];
// set is a function of ramda.js, which needs the path, the new value, the original state, and retrieves
// the altered state.
return set(
lens,
newSamples.slice(newSamples.length > limit ? newSamples.length - limit : 0),
state
);
};
// Lenses for specific data fields in the state, used for a clearer deep update.
// NOTE: This solution will be changed very likely.
const memoryLens = lensPath(['content', 'home', 'memory']);
const trafficLens = lensPath(['content', 'home', 'traffic']);
const logLens = lensPath(['content', 'logs', 'log']);
// styles retrieves the styles for the Dashboard component.
const styles = theme => ({ const styles = theme => ({
appFrame: { dashboard: {
position: 'relative', display: 'flex',
display: 'flex', flexFlow: 'column',
width: '100%', width: '100%',
height: '100%', height: '100%',
background: theme.palette.background.default, background: theme.palette.background.default,
}, zIndex: 1,
overflow: 'hidden',
},
}); });
export type Props = {
classes: Object,
};
type State = {
active: string, // active menu
sideBar: boolean, // true if the sidebar is opened
content: $Shape<Content>, // the visualized data
shouldUpdate: Set<string> // labels for the components, which need to rerender based on the incoming message
};
// Dashboard is the main component, which renders the whole page, makes connection with the server and
// listens for messages. When there is an incoming message, updates the page's content correspondingly.
class Dashboard extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
active: MENU.get('home').id,
sideBar: true,
content: {home: {memory: [], traffic: []}, logs: {log: []}},
shouldUpdate: new Set(),
};
}
// Dashboard is the main component, which renders the whole page, makes connection with the server and listens for messages. // componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
// When there is an incoming message, updates the page's content correspondingly. componentDidMount() {
class Dashboard extends Component { this.reconnect();
constructor(props) { }
super(props);
this.state = {
active: TAGS.home.id, // active menu
sideBar: true, // true if the sidebar is opened
memory: [],
traffic: [],
logs: [],
shouldUpdate: {},
};
}
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered. // reconnect establishes a websocket connection with the server, listens for incoming messages
componentDidMount() { // and tries to reconnect on connection loss.
this.reconnect(); reconnect = () => {
} this.setState({
content: {home: {memory: [], traffic: []}, logs: {log: []}},
});
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://') + window.location.host}/api`);
server.onmessage = (event) => {
const msg: Message = JSON.parse(event.data);
if (!msg) {
return;
}
this.update(msg);
};
server.onclose = () => {
setTimeout(this.reconnect, 3000);
};
};
// reconnect establishes a websocket connection with the server, listens for incoming messages // samples retrieves the raw data of a chart field from the incoming message.
// and tries to reconnect on connection loss. samples = (chart: Chart) => {
reconnect = () => { let s = [];
const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api"); if (chart.history) {
s = chart.history.map(({value}) => (value || 0)); // traffic comes without value at the beginning
}
if (chart.new) {
s = [...s, chart.new.value || 0];
}
return s;
};
server.onmessage = event => { // handleHome changes the home-menu related part of the state.
const msg = JSON.parse(event.data); handleHome = (home: HomeMessage) => {
if (isNullOrUndefined(msg)) { this.setState((prevState) => {
return; let newState = prevState;
} newState.shouldUpdate = new Set();
this.update(msg); if (home.memory) {
}; newState = appender(memoryLens, this.samples(home.memory), SAMPLE.get('memory').limit)(newState);
newState.shouldUpdate.add('memory');
}
if (home.traffic) {
newState = appender(trafficLens, this.samples(home.traffic), SAMPLE.get('traffic').limit)(newState);
newState.shouldUpdate.add('traffic');
}
return newState;
});
};
server.onclose = () => { // handleLogs changes the logs-menu related part of the state.
setTimeout(this.reconnect, 3000); handleLogs = (logs: LogsMessage) => {
}; this.setState((prevState) => {
}; let newState = prevState;
newState.shouldUpdate = new Set();
if (logs.log) {
newState = appender(logLens, [logs.log], SAMPLE.get('logs').limit)(newState);
newState.shouldUpdate.add('logs');
}
return newState;
});
};
// update analyzes the incoming message, and updates the charts' content correspondingly. // update analyzes the incoming message, and updates the charts' content correspondingly.
update = msg => { update = (msg: Message) => {
console.log(msg); if (msg.home) {
this.setState(prevState => { this.handleHome(msg.home);
let newState = []; }
newState.shouldUpdate = {}; if (msg.logs) {
const insert = (key, values, limit) => { this.handleLogs(msg.logs);
newState[key] = [...prevState[key], ...values]; }
while (newState[key].length > limit) { };
newState[key].shift();
}
newState.shouldUpdate[key] = true;
};
// (Re)initialize the state with the past data.
if (!isNullOrUndefined(msg.history)) {
const memory = DATA_KEYS.memory;
const traffic = DATA_KEYS.traffic;
newState[memory] = [];
newState[traffic] = [];
if (!isNullOrUndefined(msg.history.memorySamples)) {
newState[memory] = msg.history.memorySamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
while (newState[memory].length > LIMIT.memory) {
newState[memory].shift();
}
newState.shouldUpdate[memory] = true;
}
if (!isNullOrUndefined(msg.history.trafficSamples)) {
newState[traffic] = msg.history.trafficSamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
while (newState[traffic].length > LIMIT.traffic) {
newState[traffic].shift();
}
newState.shouldUpdate[traffic] = true;
}
}
// Insert the new data samples.
if (!isNullOrUndefined(msg.memory)) {
insert(DATA_KEYS.memory, [isNullOrUndefined(msg.memory.value) ? 0 : msg.memory.value], LIMIT.memory);
}
if (!isNullOrUndefined(msg.traffic)) {
insert(DATA_KEYS.traffic, [isNullOrUndefined(msg.traffic.value) ? 0 : msg.traffic.value], LIMIT.traffic);
}
if (!isNullOrUndefined(msg.log)) {
insert(DATA_KEYS.logs, [msg.log], LIMIT.log);
}
return newState; // changeContent sets the active label, which is used at the content rendering.
}); changeContent = (newActive: string) => {
}; this.setState(prevState => (prevState.active !== newActive ? {active: newActive} : {}));
};
// The change of the active label on the SideBar component will trigger a new render in the Main component. // openSideBar opens the sidebar.
changeContent = active => { openSideBar = () => {
this.setState(prevState => prevState.active !== active ? {active: active} : {}); this.setState({sideBar: true});
}; };
openSideBar = () => { // closeSideBar closes the sidebar.
this.setState({sideBar: true}); closeSideBar = () => {
}; this.setState({sideBar: false});
};
closeSideBar = () => { render() {
this.setState({sideBar: false}); const {classes} = this.props; // The classes property is injected by withStyles().
};
render() { return (
// The classes property is injected by withStyles(). <div className={classes.dashboard}>
const {classes} = this.props; <Header
opened={this.state.sideBar}
return ( openSideBar={this.openSideBar}
<div className={classes.appFrame}> closeSideBar={this.closeSideBar}
<Header />
opened={this.state.sideBar} <Body
open={this.openSideBar} opened={this.state.sideBar}
/> changeContent={this.changeContent}
<SideBar active={this.state.active}
opened={this.state.sideBar} content={this.state.content}
close={this.closeSideBar} shouldUpdate={this.state.shouldUpdate}
changeContent={this.changeContent} />
/> </div>
<Main );
opened={this.state.sideBar} }
active={this.state.active}
memory={this.state.memory}
traffic={this.state.traffic}
logs={this.state.logs}
shouldUpdate={this.state.shouldUpdate}
/>
</div>
);
}
} }
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Dashboard); export default withStyles(styles)(Dashboard);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -15,73 +17,89 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames'; import withStyles from 'material-ui/styles/withStyles';
import {withStyles} from 'material-ui/styles';
import AppBar from 'material-ui/AppBar'; import AppBar from 'material-ui/AppBar';
import Toolbar from 'material-ui/Toolbar'; import Toolbar from 'material-ui/Toolbar';
import Typography from 'material-ui/Typography'; import Transition from 'react-transition-group/Transition';
import IconButton from 'material-ui/IconButton'; import IconButton from 'material-ui/IconButton';
import MenuIcon from 'material-ui-icons/Menu'; import Typography from 'material-ui/Typography';
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
import {DRAWER_WIDTH} from './Common.jsx'; import {DURATION} from './Common';
// arrowDefault is the default style of the arrow button.
const arrowDefault = {
transition: `transform ${DURATION}ms`,
};
// arrowTransition is the additional style of the arrow button corresponding to the transition's state.
const arrowTransition = {
entered: {transform: 'rotate(180deg)'},
};
// Styles for the Header component. // Styles for the Header component.
const styles = theme => ({ const styles = theme => ({
appBar: { header: {
position: 'absolute', backgroundColor: theme.palette.background.appBar,
transition: theme.transitions.create(['margin', 'width'], { color: theme.palette.getContrastText(theme.palette.background.appBar),
easing: theme.transitions.easing.sharp, zIndex: theme.zIndex.appBar,
duration: theme.transitions.duration.leavingScreen, },
}), toolbar: {
}, paddingLeft: theme.spacing.unit,
appBarShift: { paddingRight: theme.spacing.unit,
marginLeft: DRAWER_WIDTH, },
width: `calc(100% - ${DRAWER_WIDTH}px)`, mainText: {
transition: theme.transitions.create(['margin', 'width'], { paddingLeft: theme.spacing.unit,
easing: theme.transitions.easing.easeOut, },
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginLeft: 12,
marginRight: 20,
},
hide: {
display: 'none',
},
}); });
export type Props = {
classes: Object,
opened: boolean,
openSideBar: () => {},
closeSideBar: () => {},
};
// Header renders the header of the dashboard.
class Header extends Component<Props> {
shouldComponentUpdate(nextProps) {
return nextProps.opened !== this.props.opened;
}
// Header renders a header, which contains a sidebar opener icon when that is closed. // changeSideBar opens or closes the sidebar corresponding to the previous state.
class Header extends Component { changeSideBar = () => {
render() { if (this.props.opened) {
// The classes property is injected by withStyles(). this.props.closeSideBar();
const {classes} = this.props; } else {
this.props.openSideBar();
}
};
return ( // arrowButton is connected to the sidebar; changes its state.
<AppBar className={classNames(classes.appBar, this.props.opened && classes.appBarShift)}> arrowButton = (transitionState: string) => (
<Toolbar disableGutters={!this.props.opened}> <IconButton onClick={this.changeSideBar}>
<IconButton <ChevronLeftIcon
color="contrast" style={{
aria-label="open drawer" ...arrowDefault,
onClick={this.props.open} ...arrowTransition[transitionState],
className={classNames(classes.menuButton, this.props.opened && classes.hide)} }}
> />
<MenuIcon /> </IconButton>
</IconButton> );
<Typography type="title" color="inherit" noWrap>
Go Ethereum Dashboard render() {
</Typography> const {classes, opened} = this.props; // The classes property is injected by withStyles().
</Toolbar>
</AppBar> return (
); <AppBar position="static" className={classes.header}>
} <Toolbar className={classes.toolbar}>
<Transition mountOnEnter in={opened} timeout={{enter: DURATION}}>
{this.arrowButton}
</Transition>
<Typography type="title" color="inherit" noWrap className={classes.mainText}>
Go Ethereum Dashboard
</Typography>
</Toolbar>
</AppBar>
);
}
} }
Header.propTypes = {
classes: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
open: PropTypes.func.isRequired,
};
export default withStyles(styles)(Header); export default withStyles(styles)(Header);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -15,75 +17,56 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Grid from 'material-ui/Grid';
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line, ResponsiveContainer} from 'recharts';
import {withTheme} from 'material-ui/styles';
import {isNullOrUndefined, DATA_KEYS} from "./Common.jsx"; import withTheme from 'material-ui/styles/withTheme';
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line} from 'recharts';
// ChartGrid renders a grid container for responsive charts. import ChartGrid from './ChartGrid';
// The children are Recharts components extended with the Material-UI's xs property. import type {ChartEntry} from '../types/message';
class ChartGrid extends Component {
render() {
return (
<Grid container spacing={this.props.spacing}>
{
React.Children.map(this.props.children, child => (
<Grid item xs={child.props.xs}>
<ResponsiveContainer width="100%" height={child.props.height}>
{React.cloneElement(child, {data: child.props.values.map(value => ({value: value}))})}
</ResponsiveContainer>
</Grid>
))
}
</Grid>
);
}
}
ChartGrid.propTypes = { export type Props = {
spacing: PropTypes.number.isRequired, theme: Object,
memory: Array<ChartEntry>,
traffic: Array<ChartEntry>,
shouldUpdate: Object,
}; };
// Home renders the home content.
class Home extends Component<Props> {
constructor(props: Props) {
super(props);
const {theme} = props; // The theme property is injected by withTheme().
this.memoryColor = theme.palette.primary[300];
this.trafficColor = theme.palette.secondary[300];
}
// Home renders the home component. shouldComponentUpdate(nextProps) {
class Home extends Component { return nextProps.shouldUpdate.has('memory') || nextProps.shouldUpdate.has('traffic');
shouldComponentUpdate(nextProps) { }
return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) ||
!isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]);
}
render() { render() {
const {theme} = this.props; const {memory, traffic} = this.props;
const memoryColor = theme.palette.primary[300];
const trafficColor = theme.palette.secondary[300];
return ( return (
<ChartGrid spacing={24}> <ChartGrid spacing={24}>
<AreaChart xs={6} height={300} values={this.props.memory}> <AreaChart xs={6} height={300} values={memory}>
<YAxis /> <YAxis />
<Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} /> <Area type="monotone" dataKey="value" stroke={this.memoryColor} fill={this.memoryColor} />
</AreaChart> </AreaChart>
<LineChart xs={6} height={300} values={this.props.traffic}> <LineChart xs={6} height={300} values={traffic}>
<Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} /> <Line type="monotone" dataKey="value" stroke={this.trafficColor} dot={false} />
</LineChart> </LineChart>
<LineChart xs={6} height={300} values={this.props.memory}> <LineChart xs={6} height={300} values={memory}>
<YAxis /> <YAxis />
<CartesianGrid stroke="#eee" strokeDasharray="5 5" /> <CartesianGrid stroke="#eee" strokeDasharray="5 5" />
<Line type="monotone" dataKey="value" stroke={memoryColor} dot={false} /> <Line type="monotone" dataKey="value" stroke={this.memoryColor} dot={false} />
</LineChart> </LineChart>
<AreaChart xs={6} height={300} values={this.props.traffic}> <AreaChart xs={6} height={300} values={traffic}>
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} /> <CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
<Area type="monotone" dataKey="value" stroke={trafficColor} fill={trafficColor} /> <Area type="monotone" dataKey="value" stroke={this.trafficColor} fill={this.trafficColor} />
</AreaChart> </AreaChart>
</ChartGrid> </ChartGrid>
); );
} }
} }
Home.propTypes = {
theme: PropTypes.object.isRequired,
shouldUpdate: PropTypes.object.isRequired,
};
export default withTheme()(Home); export default withTheme()(Home);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -15,95 +17,52 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {withStyles} from 'material-ui/styles';
import {TAGS, DRAWER_WIDTH} from "./Common.jsx"; import withStyles from 'material-ui/styles/withStyles';
import Home from './Home.jsx';
// ContentSwitch chooses and renders the proper page content. import Home from './Home';
class ContentSwitch extends Component { import {MENU} from './Common';
render() { import type {Content} from '../types/content';
switch(this.props.active) {
case TAGS.home.id:
return <Home memory={this.props.memory} traffic={this.props.traffic} shouldUpdate={this.props.shouldUpdate} />;
case TAGS.chain.id:
return null;
case TAGS.transactions.id:
return null;
case TAGS.network.id:
// Only for testing.
return null;
case TAGS.system.id:
return null;
case TAGS.logs.id:
return <div>{this.props.logs.map((log, index) => <div key={index}>{log}</div>)}</div>;
}
return null;
}
}
ContentSwitch.propTypes = { // Styles for the Content component.
active: PropTypes.string.isRequired,
shouldUpdate: PropTypes.object.isRequired,
};
// styles contains the styles for the Main component.
const styles = theme => ({ const styles = theme => ({
content: { content: {
width: '100%', flexGrow: 1,
marginLeft: -DRAWER_WIDTH, backgroundColor: theme.palette.background.default,
flexGrow: 1, padding: theme.spacing.unit * 3,
backgroundColor: theme.palette.background.default, overflow: 'auto',
padding: theme.spacing.unit * 3, },
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
marginTop: 56,
overflow: 'auto',
[theme.breakpoints.up('sm')]: {
content: {
height: 'calc(100% - 64px)',
marginTop: 64,
},
},
},
contentShift: {
marginLeft: 0,
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
},
}); });
export type Props = {
// Main renders a component for the page content. classes: Object,
class Main extends Component { active: string,
render() { content: Content,
// The classes property is injected by withStyles(). shouldUpdate: Object,
const {classes} = this.props;
return (
<main className={classNames(classes.content, this.props.opened && classes.contentShift)}>
<ContentSwitch
active={this.props.active}
memory={this.props.memory}
traffic={this.props.traffic}
logs={this.props.logs}
shouldUpdate={this.props.shouldUpdate}
/>
</main>
);
}
}
Main.propTypes = {
classes: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
active: PropTypes.string.isRequired,
shouldUpdate: PropTypes.object.isRequired,
}; };
// Main renders the chosen content.
class Main extends Component<Props> {
render() {
const {
classes, active, content, shouldUpdate,
} = this.props;
let children = null;
switch (active) {
case MENU.get('home').id:
children = <Home memory={content.home.memory} traffic={content.home.traffic} shouldUpdate={shouldUpdate} />;
break;
case MENU.get('chain').id:
case MENU.get('txpool').id:
case MENU.get('network').id:
case MENU.get('system').id:
children = <div>Work in progress.</div>;
break;
case MENU.get('logs').id:
children = <div>{content.logs.log.map((log, index) => <div key={index}>{log}</div>)}</div>;
}
return <div className={classes.content}>{children}</div>;
}
}
export default withStyles(styles)(Main); export default withStyles(styles)(Main);

View file

@ -1,3 +1,5 @@
// @flow
// Copyright 2017 The go-ethereum Authors // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -15,92 +17,106 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withStyles} from 'material-ui/styles';
import Drawer from 'material-ui/Drawer';
import {IconButton} from "material-ui";
import List, {ListItem, ListItemText} from 'material-ui/List';
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
import {TAGS, DRAWER_WIDTH} from './Common.jsx'; import withStyles from 'material-ui/styles/withStyles';
import List, {ListItem, ListItemIcon, ListItemText} from 'material-ui/List';
import Icon from 'material-ui/Icon';
import Transition from 'react-transition-group/Transition';
import {Icon as FontAwesome} from 'react-fa';
import {MENU, DURATION} from './Common';
// menuDefault is the default style of the menu.
const menuDefault = {
transition: `margin-left ${DURATION}ms`,
};
// menuTransition is the additional style of the menu corresponding to the transition's state.
const menuTransition = {
entered: {marginLeft: -200},
};
// Styles for the SideBar component. // Styles for the SideBar component.
const styles = theme => ({ const styles = theme => ({
drawerPaper: { list: {
position: 'relative', background: theme.palette.background.appBar,
height: '100%', },
width: DRAWER_WIDTH, listItem: {
}, minWidth: theme.spacing.unit * 3,
drawerHeader: { },
display: 'flex', icon: {
alignItems: 'center', fontSize: theme.spacing.unit * 3,
justifyContent: 'flex-end', },
padding: '0 8px',
...theme.mixins.toolbar,
transitionDuration: {
enter: theme.transitions.duration.enteringScreen,
exit: theme.transitions.duration.leavingScreen,
}
},
}); });
export type Props = {
classes: Object,
opened: boolean,
changeContent: () => {},
};
// SideBar renders the sidebar of the dashboard.
class SideBar extends Component<Props> {
constructor(props) {
super(props);
// SideBar renders a sidebar component. // clickOn contains onClick event functions for the menu items.
class SideBar extends Component { // Instantiate only once, and reuse the existing functions to prevent the creation of
constructor(props) { // new function instances every time the render method is triggered.
super(props); this.clickOn = {};
MENU.forEach((menu) => {
this.clickOn[menu.id] = (event) => {
event.preventDefault();
props.changeContent(menu.id);
};
});
}
// clickOn contains onClick event functions for the menu items. shouldComponentUpdate(nextProps) {
// Instantiate only once, and reuse the existing functions to prevent the creation of return nextProps.opened !== this.props.opened;
// new function instances every time the render method is triggered. }
this.clickOn = {};
for(let key in TAGS) {
const id = TAGS[key].id;
this.clickOn[id] = event => {
event.preventDefault();
console.log(event.target.key);
this.props.changeContent(id);
};
}
}
render() { menuItems = (transitionState) => {
// The classes property is injected by withStyles(). const {classes} = this.props;
const {classes} = this.props; const children = [];
MENU.forEach((menu) => {
children.push(
<ListItem button key={menu.id} onClick={this.clickOn[menu.id]} className={classes.listItem}>
<ListItemIcon>
<Icon className={classes.icon}>
<FontAwesome name={menu.icon} />
</Icon>
</ListItemIcon>
<ListItemText
primary={menu.title}
style={{
...menuDefault,
...menuTransition[transitionState],
padding: 0,
}}
/>
</ListItem>,
);
});
return children;
};
return ( // menu renders the list of the menu items.
<Drawer menu = (transitionState) => {
type="persistent" const {classes} = this.props; // The classes property is injected by withStyles().
classes={{paper: classes.drawerPaper,}}
open={this.props.opened} return (
> <div className={classes.list}>
<div> <List>
<div className={classes.drawerHeader}> {this.menuItems(transitionState)}
<IconButton onClick={this.props.close}> </List>
<ChevronLeftIcon /> </div>
</IconButton> );
</div> };
<List>
{ render() {
Object.values(TAGS).map(tag => { return (
return ( <Transition mountOnEnter in={this.props.opened} timeout={{enter: DURATION}}>
<ListItem button key={tag.id} onClick={this.clickOn[tag.id]}> {this.menu}
<ListItemText primary={tag.title} /> </Transition>
</ListItem> );
); }
})
}
</List>
</div>
</Drawer>
);
}
} }
SideBar.propTypes = {
classes: PropTypes.object.isRequired,
opened: PropTypes.bool.isRequired,
close: PropTypes.func.isRequired,
changeContent: PropTypes.func.isRequired,
};
export default withStyles(styles)(SideBar); export default withStyles(styles)(SideBar);

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 // Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library. // This file is part of the go-ethereum library.
// //
@ -15,22 +17,25 @@
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
import React from 'react'; import React from 'react';
import {hydrate} from 'react-dom'; import {render} from 'react-dom';
import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles';
import Dashboard from './components/Dashboard.jsx'; import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import createMuiTheme from 'material-ui/styles/createMuiTheme';
import Dashboard from './components/Dashboard';
// Theme for the dashboard.
const theme = createMuiTheme({ const theme = createMuiTheme({
palette: { palette: {
type: 'dark', type: 'dark',
}, },
}); });
const dashboard = document.getElementById('dashboard');
// Renders the whole dashboard. if (dashboard) {
hydrate( // Renders the whole dashboard.
<MuiThemeProvider theme={theme}> render(
<Dashboard /> <MuiThemeProvider theme={theme}>
</MuiThemeProvider>, <Dashboard />
document.getElementById('dashboard') </MuiThemeProvider>,
); dashboard,
);
}

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

File diff suppressed because it is too large Load diff

View file

@ -1,22 +1,41 @@
{ {
"dependencies": { "dependencies": {
"babel-core": "^6.26.0", "babel-core": "^6.26.0",
"babel-eslint": "^8.0.1", "babel-eslint": "^8.0.3",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",
"babel-preset-env": "^1.6.1", "babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-react": "^6.24.1", "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-stage-0": "^6.24.1", "babel-plugin-transform-flow-strip-types": "^6.22.0",
"classnames": "^2.2.5", "babel-plugin-transform-runtime": "^6.23.0",
"eslint": "^4.5.0", "babel-preset-env": "^1.6.1",
"eslint-plugin-react": "^7.4.0", "babel-preset-react": "^6.24.1",
"material-ui": "^1.0.0-beta.18", "babel-preset-stage-0": "^6.24.1",
"material-ui-icons": "^1.0.0-beta.17", "babel-runtime": "^6.26.0",
"path": "^0.12.7", "classnames": "^2.2.5",
"prop-types": "^15.6.0", "css-loader": "^0.28.7",
"recharts": "^1.0.0-beta.0", "eslint": "^4.13.1",
"react": "^16.0.0", "eslint-config-airbnb": "^16.1.0",
"react-dom": "^16.0.0", "eslint-loader": "^1.9.0",
"url": "^0.11.0", "eslint-plugin-import": "^2.8.0",
"webpack": "^3.5.5" "eslint-plugin-jsx-a11y": "^6.0.3",
} "eslint-plugin-react": "^7.5.1",
"eslint-plugin-flowtype": "^2.40.1",
"file-loader": "^1.1.6",
"flow-bin": "^0.61.0",
"flow-bin-loader": "^1.0.2",
"flow-typed": "^2.2.3",
"material-ui": "^1.0.0-beta.24",
"material-ui-icons": "^1.0.0-beta.17",
"path": "^0.12.7",
"ramda": "^0.25.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-fa": "^5.0.0",
"react-transition-group": "^2.2.1",
"recharts": "^1.0.0-beta.6",
"style-loader": "^0.19.1",
"url": "^0.11.0",
"url-loader": "^0.6.2",
"webpack": "^3.10.0"
}
} }

View file

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

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,23 +14,61 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
const webpack = require('webpack');
const path = require('path'); const path = require('path');
module.exports = { module.exports = {
entry: './index.jsx', resolve: {
output: { extensions: ['.js', '.jsx'],
path: path.resolve(__dirname, 'public'), },
filename: 'bundle.js', entry: './index',
}, output: {
module: { path: path.resolve(__dirname, 'public'),
loaders: [ filename: 'bundle.js',
{ },
test: /\.jsx$/, // regexp for JSX files plugins: [
loader: 'babel-loader', // The babel configuration is in the package.json. new webpack.optimize.UglifyJsPlugin({
query: { comments: false,
presets: ['env', 'react', 'stage-0'] mangle: false,
} beautify: true,
}, }),
], ],
}, module: {
rules: [
{
test: /\.jsx$/, // regexp for JSX files
exclude: /node_modules/,
use: [ // order: from bottom to top
{
loader: 'babel-loader',
options: {
plugins: [ // order: from top to bottom
// 'transform-decorators-legacy', // @withStyles, @withTheme
'transform-class-properties', // static defaultProps
'transform-flow-strip-types',
],
presets: [ // order: from bottom to top
'env',
'react',
'stage-0',
],
},
},
// 'eslint-loader', // show errors not only in the editor, but also in the console
],
},
{
test: /font-awesome\.css$/,
use: [
'style-loader',
'css-loader',
path.resolve(__dirname, './fa-only-woff-loader.js'),
],
},
{
test: /\.woff2?$/, // font-awesome icons
use: 'url-loader',
},
],
},
}; };

View file

@ -16,7 +16,10 @@
package dashboard package dashboard
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets/public/... //go:generate ./assets/node_modules/.bin/webpack --config ./assets/webpack.config.js --context ./assets
//go:generate go-bindata -nometadata -o assets.go -prefix assets -nocompress -pkg dashboard assets/public/...
//go:generate gofmt -s -w assets.go
//go:generate sed -i "s#var _public#//nolint:misspell\\n&#" assets.go
import ( import (
"fmt" "fmt"
@ -40,7 +43,7 @@ const (
trafficSampleLimit = 200 // Maximum number of traffic data samples trafficSampleLimit = 200 // Maximum number of traffic data samples
) )
var nextId uint32 // Next connection id var nextID uint32 // Next connection id
// Dashboard contains the dashboard internals. // Dashboard contains the dashboard internals.
type Dashboard struct { type Dashboard struct {
@ -48,46 +51,30 @@ type Dashboard struct {
listener net.Listener listener net.Listener
conns map[uint32]*client // Currently live websocket connections conns map[uint32]*client // Currently live websocket connections
charts charts // The collected data samples to plot charts *HomeMessage
lock sync.RWMutex // Lock protecting the dashboard's internals lock sync.RWMutex // Lock protecting the dashboard's internals
quit chan chan error // Channel used for graceful exit quit chan chan error // Channel used for graceful exit
wg sync.WaitGroup wg sync.WaitGroup
} }
// message embraces the data samples of a client message.
type message struct {
History *charts `json:"history,omitempty"` // Past data samples
Memory *chartEntry `json:"memory,omitempty"` // One memory sample
Traffic *chartEntry `json:"traffic,omitempty"` // One traffic sample
Log string `json:"log,omitempty"` // One log
}
// client represents active websocket connection with a remote browser. // client represents active websocket connection with a remote browser.
type client struct { type client struct {
conn *websocket.Conn // Particular live websocket connection conn *websocket.Conn // Particular live websocket connection
msg chan message // Message queue for the update messages msg chan Message // Message queue for the update messages
logger log.Logger // Logger for the particular live websocket connection logger log.Logger // Logger for the particular live websocket connection
} }
// charts contains the collected data samples.
type charts struct {
Memory []*chartEntry `json:"memorySamples,omitempty"`
Traffic []*chartEntry `json:"trafficSamples,omitempty"`
}
// chartEntry represents one data sample
type chartEntry struct {
Time time.Time `json:"time,omitempty"`
Value float64 `json:"value,omitempty"`
}
// New creates a new dashboard instance with the given configuration. // New creates a new dashboard instance with the given configuration.
func New(config *Config) (*Dashboard, error) { func New(config *Config) (*Dashboard, error) {
return &Dashboard{ return &Dashboard{
conns: make(map[uint32]*client), conns: make(map[uint32]*client),
config: config, config: config,
quit: make(chan chan error), quit: make(chan chan error),
charts: &HomeMessage{
Memory: &Chart{},
Traffic: &Chart{},
},
}, nil }, nil
} }
@ -183,13 +170,13 @@ func (db *Dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
// apiHandler handles requests for the dashboard. // apiHandler handles requests for the dashboard.
func (db *Dashboard) apiHandler(conn *websocket.Conn) { func (db *Dashboard) apiHandler(conn *websocket.Conn) {
id := atomic.AddUint32(&nextId, 1) id := atomic.AddUint32(&nextID, 1)
client := &client{ client := &client{
conn: conn, conn: conn,
msg: make(chan message, 128), msg: make(chan Message, 128),
logger: log.New("id", id), logger: log.New("id", id),
} }
done := make(chan struct{}) // Buffered channel as sender may exit early done := make(chan struct{})
// Start listening for messages to send. // Start listening for messages to send.
db.wg.Add(1) db.wg.Add(1)
@ -210,8 +197,15 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
} }
}() }()
// Send the past data. // Send the past data.
client.msg <- message{ client.msg <- Message{
History: &db.charts, Home: &HomeMessage{
Memory: &Chart{
History: db.charts.Memory.History,
},
Traffic: &Chart{
History: db.charts.Traffic.History,
},
},
} }
// Start tracking the connection and drop at connection loss. // Start tracking the connection and drop at connection loss.
db.lock.Lock() db.lock.Lock()
@ -245,29 +239,34 @@ func (db *Dashboard) collectData() {
inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1() inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1() memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
now := time.Now() now := time.Now()
memory := &chartEntry{ memory := &ChartEntry{
Time: now, Time: now,
Value: memoryInUse, Value: memoryInUse,
} }
traffic := &chartEntry{ traffic := &ChartEntry{
Time: now, Time: now,
Value: inboundTraffic, Value: inboundTraffic,
} }
// Remove the first elements in case the samples' amount exceeds the limit.
first := 0 first := 0
if len(db.charts.Memory) == memorySampleLimit { if len(db.charts.Memory.History) == memorySampleLimit {
first = 1 first = 1
} }
db.charts.Memory = append(db.charts.Memory[first:], memory) db.charts.Memory.History = append(db.charts.Memory.History[first:], memory)
first = 0 first = 0
if len(db.charts.Traffic) == trafficSampleLimit { if len(db.charts.Traffic.History) == trafficSampleLimit {
first = 1 first = 1
} }
db.charts.Traffic = append(db.charts.Traffic[first:], traffic) db.charts.Traffic.History = append(db.charts.Traffic.History[first:], traffic)
db.sendToAll(&message{ db.sendToAll(&Message{
Memory: memory, Home: &HomeMessage{
Traffic: traffic, Memory: &Chart{
New: memory,
},
Traffic: &Chart{
New: traffic,
},
},
}) })
} }
} }
@ -277,6 +276,7 @@ func (db *Dashboard) collectData() {
func (db *Dashboard) collectLogs() { func (db *Dashboard) collectLogs() {
defer db.wg.Done() defer db.wg.Done()
id := 1
// TODO (kurkomisi): log collection comes here. // TODO (kurkomisi): log collection comes here.
for { for {
select { select {
@ -284,15 +284,18 @@ func (db *Dashboard) collectLogs() {
errc <- nil errc <- nil
return return
case <-time.After(db.config.Refresh / 2): case <-time.After(db.config.Refresh / 2):
db.sendToAll(&message{ db.sendToAll(&Message{
Log: "This is a fake log.", Logs: &LogsMessage{
Log: fmt.Sprintf("%-4d: This is a fake log.", id),
},
}) })
id++
} }
} }
} }
// sendToAll sends the given message to the active dashboards. // sendToAll sends the given message to the active dashboards.
func (db *Dashboard) sendToAll(msg *message) { func (db *Dashboard) sendToAll(msg *Message) {
db.lock.Lock() db.lock.Lock()
for _, c := range db.conns { for _, c := range db.conns {
select { select {

63
dashboard/message.go Normal file
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 package eth
import ( import (
"bytes"
"compress/gzip" "compress/gzip"
"context" "context"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math/big" "math/big"
"os" "os"
"strings" "strings"
"time"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/common/hexutil" "github.com/EthereumCommonwealth/go-callisto/common/hexutil"
"github.com/EthereumCommonwealth/go-callisto/core" "github.com/EthereumCommonwealth/go-callisto/core"
"github.com/EthereumCommonwealth/go-callisto/core/state" "github.com/EthereumCommonwealth/go-callisto/core/state"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
"github.com/EthereumCommonwealth/go-callisto/core/vm"
"github.com/EthereumCommonwealth/go-callisto/internal/ethapi"
"github.com/EthereumCommonwealth/go-callisto/log" "github.com/EthereumCommonwealth/go-callisto/log"
"github.com/EthereumCommonwealth/go-callisto/miner" "github.com/EthereumCommonwealth/go-callisto/miner"
"github.com/EthereumCommonwealth/go-callisto/params" "github.com/EthereumCommonwealth/go-callisto/params"
@ -43,8 +38,6 @@ import (
"github.com/EthereumCommonwealth/go-callisto/trie" "github.com/EthereumCommonwealth/go-callisto/trie"
) )
const defaultTraceTimeout = 5 * time.Second
// PublicEthereumAPI provides an API to access Ethereum full node-related // PublicEthereumAPI provides an API to access Ethereum full node-related
// information. // information.
type PublicEthereumAPI struct { type PublicEthereumAPI struct {
@ -348,248 +341,6 @@ func NewPrivateDebugAPI(config *params.ChainConfig, eth *Ethereum) *PrivateDebug
return &PrivateDebugAPI{config: config, eth: eth} return &PrivateDebugAPI{config: config, eth: eth}
} }
// BlockTraceResult is the returned value when replaying a block to check for
// consensus results and full VM trace logs for all included transactions.
type BlockTraceResult struct {
Validated bool `json:"validated"`
StructLogs []ethapi.StructLogRes `json:"structLogs"`
Error string `json:"error"`
}
// TraceArgs holds extra parameters to trace functions
type TraceArgs struct {
*vm.LogConfig
Tracer *string
Timeout *string
}
// TraceBlock processes the given block'api RLP but does not import the block in to
// the chain.
func (api *PrivateDebugAPI) TraceBlock(blockRlp []byte, config *vm.LogConfig) BlockTraceResult {
var block types.Block
err := rlp.Decode(bytes.NewReader(blockRlp), &block)
if err != nil {
return BlockTraceResult{Error: fmt.Sprintf("could not decode block: %v", err)}
}
validated, logs, err := api.traceBlock(&block, config)
return BlockTraceResult{
Validated: validated,
StructLogs: ethapi.FormatLogs(logs),
Error: formatError(err),
}
}
// TraceBlockFromFile loads the block'api RLP from the given file name and attempts to
// process it but does not import the block in to the chain.
func (api *PrivateDebugAPI) TraceBlockFromFile(file string, config *vm.LogConfig) BlockTraceResult {
blockRlp, err := ioutil.ReadFile(file)
if err != nil {
return BlockTraceResult{Error: fmt.Sprintf("could not read file: %v", err)}
}
return api.TraceBlock(blockRlp, config)
}
// TraceBlockByNumber processes the block by canonical block number.
func (api *PrivateDebugAPI) TraceBlockByNumber(blockNr rpc.BlockNumber, config *vm.LogConfig) BlockTraceResult {
// Fetch the block that we aim to reprocess
var block *types.Block
switch blockNr {
case rpc.PendingBlockNumber:
// Pending block is only known by the miner
block = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber:
block = api.eth.blockchain.CurrentBlock()
default:
block = api.eth.blockchain.GetBlockByNumber(uint64(blockNr))
}
if block == nil {
return BlockTraceResult{Error: fmt.Sprintf("block #%d not found", blockNr)}
}
validated, logs, err := api.traceBlock(block, config)
return BlockTraceResult{
Validated: validated,
StructLogs: ethapi.FormatLogs(logs),
Error: formatError(err),
}
}
// TraceBlockByHash processes the block by hash.
func (api *PrivateDebugAPI) TraceBlockByHash(hash common.Hash, config *vm.LogConfig) BlockTraceResult {
// Fetch the block that we aim to reprocess
block := api.eth.BlockChain().GetBlockByHash(hash)
if block == nil {
return BlockTraceResult{Error: fmt.Sprintf("block #%x not found", hash)}
}
validated, logs, err := api.traceBlock(block, config)
return BlockTraceResult{
Validated: validated,
StructLogs: ethapi.FormatLogs(logs),
Error: formatError(err),
}
}
// traceBlock processes the given block but does not save the state.
func (api *PrivateDebugAPI) traceBlock(block *types.Block, logConfig *vm.LogConfig) (bool, []vm.StructLog, error) {
// Validate and reprocess the block
var (
blockchain = api.eth.BlockChain()
validator = blockchain.Validator()
processor = blockchain.Processor()
)
structLogger := vm.NewStructLogger(logConfig)
config := vm.Config{
Debug: true,
Tracer: structLogger,
}
if err := api.eth.engine.VerifyHeader(blockchain, block.Header(), true); err != nil {
return false, structLogger.StructLogs(), err
}
statedb, err := blockchain.StateAt(blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1).Root())
if err != nil {
switch err.(type) {
case *trie.MissingNodeError:
return false, structLogger.StructLogs(), fmt.Errorf("required historical state unavailable")
default:
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 {
switch err.(type) {
case *trie.MissingNodeError:
return nil, fmt.Errorf("required historical state unavailable")
default:
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. // Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
db := core.PreimageTable(api.eth.ChainDb()) db := core.PreimageTable(api.eth.ChainDb())
@ -617,7 +368,7 @@ type storageEntry struct {
// StorageRangeAt returns the storage at the given block height and transaction index. // StorageRangeAt returns the storage at the given block height and transaction index.
func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) {
_, _, statedb, err := api.computeTxEnv(blockHash, txIndex) _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0)
if err != nil { if err != nil {
return StorageRangeResult{}, err return StorageRangeResult{}, err
} }

727
eth/api_tracer.go Normal file
View file

@ -0,0 +1,727 @@
// 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 eth
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/common/hexutil"
"github.com/EthereumCommonwealth/go-callisto/core"
"github.com/EthereumCommonwealth/go-callisto/core/state"
"github.com/EthereumCommonwealth/go-callisto/core/types"
"github.com/EthereumCommonwealth/go-callisto/core/vm"
"github.com/EthereumCommonwealth/go-callisto/eth/tracers"
"github.com/EthereumCommonwealth/go-callisto/ethdb"
"github.com/EthereumCommonwealth/go-callisto/internal/ethapi"
"github.com/EthereumCommonwealth/go-callisto/log"
"github.com/EthereumCommonwealth/go-callisto/rlp"
"github.com/EthereumCommonwealth/go-callisto/rpc"
"github.com/EthereumCommonwealth/go-callisto/trie"
)
const (
// defaultTraceTimeout is the amount of time a single transaction can execute
// by default before being forcefully aborted.
defaultTraceTimeout = 5 * time.Second
// defaultTraceReexec is the number of blocks the tracer is willing to go back
// and reexecute to produce missing historical state necessary to run a specific
// trace.
defaultTraceReexec = uint64(128)
)
// TraceConfig holds extra parameters to trace functions.
type TraceConfig struct {
*vm.LogConfig
Tracer *string
Timeout *string
Reexec *uint64
}
// txTraceResult is the result of a single transaction trace.
type txTraceResult struct {
Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer
Error string `json:"error,omitempty"` // Trace failure produced by the tracer
}
// blockTraceTask represents a single block trace task when an entire chain is
// being traced.
type blockTraceTask struct {
statedb *state.StateDB // Intermediate state prepped for tracing
block *types.Block // Block to trace the transactions from
results []*txTraceResult // Trace results procudes by the task
}
// blockTraceResult represets the results of tracing a single block when an entire
// chain is being traced.
type blockTraceResult struct {
Block hexutil.Uint64 `json:"block"` // Block number corresponding to this trace
Hash common.Hash `json:"hash"` // Block hash corresponding to this trace
Traces []*txTraceResult `json:"traces"` // Trace results produced by the task
}
// txTraceTask represents a single transaction trace task when an entire block
// is being traced.
type txTraceTask struct {
statedb *state.StateDB // Intermediate state prepped for tracing
index int // Transaction offset in the block
}
// ephemeralDatabase is a memory wrapper around a proper database, which acts as
// an ephemeral write layer. This construct is used by the chain tracer to write
// state tries for intermediate blocks without serializing to disk, but at the
// same time to allow disk fallback for reads that do no hit the memory layer.
type ephemeralDatabase struct {
diskdb ethdb.Database // Persistent disk database to fall back to with reads
memdb *ethdb.MemDatabase // Ephemeral memory database for primary reads and writes
}
func (db *ephemeralDatabase) Put(key []byte, value []byte) error { return db.memdb.Put(key, value) }
func (db *ephemeralDatabase) Delete(key []byte) error { return errors.New("delete not supported") }
func (db *ephemeralDatabase) Close() { db.memdb.Close() }
func (db *ephemeralDatabase) NewBatch() ethdb.Batch {
return db.memdb.NewBatch()
}
func (db *ephemeralDatabase) Has(key []byte) (bool, error) {
if has, _ := db.memdb.Has(key); has {
return has, nil
}
return db.diskdb.Has(key)
}
func (db *ephemeralDatabase) Get(key []byte) ([]byte, error) {
if blob, _ := db.memdb.Get(key); blob != nil {
return blob, nil
}
return db.diskdb.Get(key)
}
// Prune does a state sync into a new memory write layer and replaces the old one.
// This allows us to discard entries that are no longer referenced from the current
// state.
func (db *ephemeralDatabase) Prune(root common.Hash) {
// Pull the still relevant state data into memory
sync := state.NewStateSync(root, db.diskdb)
for sync.Pending() > 0 {
hash := sync.Missing(1)[0]
// Move the next trie node from the memory layer into a sync struct
node, err := db.memdb.Get(hash[:])
if err != nil {
panic(err) // memdb must have the data
}
if _, _, err := sync.Process([]trie.SyncResult{{Hash: hash, Data: node}}); err != nil {
panic(err) // it's not possible to fail processing a node
}
}
// Discard the old memory layer and write a new one
db.memdb, _ = ethdb.NewMemDatabaseWithCap(db.memdb.Len())
if _, err := sync.Commit(db); err != nil {
panic(err) // writing into a memdb cannot fail
}
}
// TraceChain returns the structured logs created during the execution of EVM
// between two blocks (excluding start) and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) {
// Fetch the block interval that we want to trace
var from, to *types.Block
switch start {
case rpc.PendingBlockNumber:
from = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber:
from = api.eth.blockchain.CurrentBlock()
default:
from = api.eth.blockchain.GetBlockByNumber(uint64(start))
}
switch end {
case rpc.PendingBlockNumber:
to = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber:
to = api.eth.blockchain.CurrentBlock()
default:
to = api.eth.blockchain.GetBlockByNumber(uint64(end))
}
// Trace the chain if we've found all our blocks
if from == nil {
return nil, fmt.Errorf("starting block #%d not found", start)
}
if to == nil {
return nil, fmt.Errorf("end block #%d not found", end)
}
return api.traceChain(ctx, from, to, config)
}
// traceChain configures a new tracer according to the provided configuration, and
// executes all the transactions contained within. The return value will be one item
// per transaction, dependent on the requestd tracer.
func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) {
// Tracing a chain is a **long** operation, only do with subscriptions
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
}
sub := notifier.CreateSubscription()
// Ensure we have a valid starting state before doing any work
origin := start.NumberU64()
memdb, _ := ethdb.NewMemDatabase()
db := &ephemeralDatabase{
diskdb: api.eth.ChainDb(),
memdb: memdb,
}
if number := start.NumberU64(); number > 0 {
start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
if start == nil {
return nil, fmt.Errorf("parent block #%d not found", number-1)
}
}
statedb, err := state.New(start.Root(), state.NewDatabase(db))
if err != nil {
// If the starting state is missing, allow some number of blocks to be reexecuted
reexec := defaultTraceReexec
if config.Reexec != nil {
reexec = *config.Reexec
}
// Find the most recent block that has the state available
for i := uint64(0); i < reexec; i++ {
start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1)
if start == nil {
break
}
if statedb, err = state.New(start.Root(), state.NewDatabase(db)); err == nil {
break
}
}
// If we still don't have the state available, bail out
if err != nil {
switch err.(type) {
case *trie.MissingNodeError:
return nil, errors.New("required historical state unavailable")
default:
return nil, err
}
}
}
// Execute all the transaction contained within the chain concurrently for each block
blocks := int(end.NumberU64() - origin)
threads := runtime.NumCPU()
if threads > blocks {
threads = blocks
}
var (
pend = new(sync.WaitGroup)
tasks = make(chan *blockTraceTask, threads)
results = make(chan *blockTraceTask, threads)
)
for th := 0; th < threads; th++ {
pend.Add(1)
go func() {
defer pend.Done()
// Fetch and execute the next block trace tasks
for task := range tasks {
signer := types.MakeSigner(api.config, task.block.Number())
// Trace all the transactions contained within
for i, tx := range task.block.Transactions() {
msg, _ := tx.AsMessage(signer)
vmctx := core.NewEVMContext(msg, task.block.Header(), api.eth.blockchain, nil)
res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
if err != nil {
task.results[i] = &txTraceResult{Error: err.Error()}
log.Warn("Tracing failed", "err", err)
break
}
task.statedb.DeleteSuicides()
task.results[i] = &txTraceResult{Result: res}
}
// Stream the result back to the user or abort on teardown
select {
case results <- task:
case <-notifier.Closed():
return
}
}
}()
}
// Start a goroutine to feed all the blocks into the tracers
begin := time.Now()
complete := start.NumberU64()
go func() {
var (
logged time.Time
number uint64
traced uint64
failed error
)
// Ensure everything is properly cleaned up on any exit path
defer func() {
close(tasks)
pend.Wait()
switch {
case failed != nil:
log.Warn("Chain tracing failed", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin), "err", failed)
case number < end.NumberU64():
log.Warn("Chain tracing aborted", "start", start.NumberU64(), "end", end.NumberU64(), "abort", number, "transactions", traced, "elapsed", time.Since(begin))
default:
log.Info("Chain tracing finished", "start", start.NumberU64(), "end", end.NumberU64(), "transactions", traced, "elapsed", time.Since(begin))
}
close(results)
}()
// Feed all the blocks both into the tracer, as well as fast process concurrently
for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ {
// Stop tracing if interruption was requested
select {
case <-notifier.Closed():
return
default:
}
// Print progress logs if long enough time elapsed
if time.Since(logged) > 8*time.Second {
if number > origin {
log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin))
} else {
log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin))
}
logged = time.Now()
}
// Retrieve the next block to trace
block := api.eth.blockchain.GetBlockByNumber(number)
if block == nil {
failed = fmt.Errorf("block #%d not found", number)
break
}
// Send the block over to the concurrent tracers (if not in the fast-forward phase)
if number > origin {
txs := block.Transactions()
select {
case tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, results: make([]*txTraceResult, len(txs))}:
case <-notifier.Closed():
return
}
traced += uint64(len(txs))
} else {
atomic.StoreUint64(&complete, number)
}
// Generate the next state snapshot fast without tracing
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
if err != nil {
failed = err
break
}
// Finalize the state so any modifications are written to the trie
root, err := statedb.CommitTo(db, true)
if err != nil {
failed = err
break
}
if err := statedb.Reset(root); err != nil {
failed = err
break
}
// After every N blocks, prune the database to only retain relevant data
if (number-start.NumberU64())%4096 == 0 {
// Wait until currently pending trace jobs finish
for atomic.LoadUint64(&complete) != number {
select {
case <-time.After(100 * time.Millisecond):
case <-notifier.Closed():
return
}
}
// No more concurrent access at this point, prune the database
var (
nodes = db.memdb.Len()
start = time.Now()
)
db.Prune(root)
log.Info("Pruned tracer state entries", "deleted", nodes-db.memdb.Len(), "left", db.memdb.Len(), "elapsed", time.Since(start))
statedb, _ = state.New(root, state.NewDatabase(db))
}
}
}()
// Keep reading the trace results and stream the to the user
go func() {
var (
done = make(map[uint64]*blockTraceResult)
next = origin + 1
)
for res := range results {
// Queue up next received result
result := &blockTraceResult{
Block: hexutil.Uint64(res.block.NumberU64()),
Hash: res.block.Hash(),
Traces: res.results,
}
done[uint64(result.Block)] = result
// Stream completed traces to the user, aborting on the first error
for result, ok := done[next]; ok; result, ok = done[next] {
if len(result.Traces) > 0 || next == end.NumberU64() {
notifier.Notify(sub.ID, result)
}
atomic.StoreUint64(&complete, next)
delete(done, next)
next++
}
}
}()
return sub, nil
}
// TraceBlockByNumber returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) {
// Fetch the block that we want to trace
var block *types.Block
switch number {
case rpc.PendingBlockNumber:
block = api.eth.miner.PendingBlock()
case rpc.LatestBlockNumber:
block = api.eth.blockchain.CurrentBlock()
default:
block = api.eth.blockchain.GetBlockByNumber(uint64(number))
}
// Trace the block if it was found
if block == nil {
return nil, fmt.Errorf("block #%d not found", number)
}
return api.traceBlock(ctx, block, config)
}
// TraceBlockByHash returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
block := api.eth.blockchain.GetBlockByHash(hash)
if block == nil {
return nil, fmt.Errorf("block #%x not found", hash)
}
return api.traceBlock(ctx, block, config)
}
// TraceBlock returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) {
block := new(types.Block)
if err := rlp.Decode(bytes.NewReader(blob), block); err != nil {
return nil, fmt.Errorf("could not decode block: %v", err)
}
return api.traceBlock(ctx, block, config)
}
// TraceBlockFromFile returns the structured logs created during the execution of
// EVM and returns them as a JSON object.
func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) {
blob, err := ioutil.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("could not read file: %v", err)
}
return api.TraceBlock(ctx, blob, config)
}
// traceBlock configures a new tracer according to the provided configuration, and
// executes all the transactions contained within. The return value will be one item
// per transaction, dependent on the requestd tracer.
func (api *PrivateDebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) {
// Create the parent state database
if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil {
return nil, err
}
parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return nil, fmt.Errorf("parent %x not found", block.ParentHash())
}
reexec := defaultTraceReexec
if config.Reexec != nil {
reexec = *config.Reexec
}
statedb, err := api.computeStateDB(parent, reexec)
if err != nil {
return nil, err
}
// Execute all the transaction contained within the block concurrently
var (
signer = types.MakeSigner(api.config, block.Number())
txs = block.Transactions()
results = make([]*txTraceResult, len(txs))
pend = new(sync.WaitGroup)
jobs = make(chan *txTraceTask, len(txs))
)
threads := runtime.NumCPU()
if threads > len(txs) {
threads = len(txs)
}
for th := 0; th < threads; th++ {
pend.Add(1)
go func() {
defer pend.Done()
// Fetch and execute the next transaction trace tasks
for task := range jobs {
msg, _ := txs[task.index].AsMessage(signer)
vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
res, err := api.traceTx(ctx, msg, vmctx, task.statedb, config)
if err != nil {
results[task.index] = &txTraceResult{Error: err.Error()}
continue
}
results[task.index] = &txTraceResult{Result: res}
}
}()
}
// Feed the transactions into the tracers and return
var failed error
for i, tx := range txs {
// Send the trace task over for execution
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i}
// Generate the next state snapshot fast without tracing
msg, _ := tx.AsMessage(signer)
vmctx := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
vmenv := vm.NewEVM(vmctx, statedb, api.config, vm.Config{})
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil {
failed = err
break
}
// Finalize the state so any modifications are written to the trie
statedb.Finalise(true)
}
close(jobs)
pend.Wait()
// If execution failed in between, abort
if failed != nil {
return nil, failed
}
return results, nil
}
// computeStateDB retrieves the state database associated with a certain block.
// If no state is locally available for the given block, a number of blocks are
// attempted to be reexecuted to generate the desired state.
func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, error) {
// If we have the state fully available, use that
statedb, err := api.eth.blockchain.StateAt(block.Root())
if err == nil {
return statedb, nil
}
// Otherwise try to reexec blocks until we find a state or reach our limit
origin := block.NumberU64()
memdb, _ := ethdb.NewMemDatabase()
db := &ephemeralDatabase{
diskdb: api.eth.ChainDb(),
memdb: memdb,
}
for i := uint64(0); i < reexec; i++ {
block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1)
if block == nil {
break
}
if statedb, err = state.New(block.Root(), state.NewDatabase(db)); err == nil {
break
}
}
if err != nil {
switch err.(type) {
case *trie.MissingNodeError:
return nil, errors.New("required historical state unavailable")
default:
return nil, err
}
}
// State was available at historical point, regenerate
var (
start = time.Now()
logged time.Time
)
for block.NumberU64() < origin {
// Print progress logs if long enough time elapsed
if time.Since(logged) > 8*time.Second {
log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "elapsed", time.Since(start))
logged = time.Now()
}
// Retrieve the next block to regenerate and process it
if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil {
return nil, fmt.Errorf("block #%d not found", block.NumberU64()+1)
}
_, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, vm.Config{})
if err != nil {
return nil, err
}
// Finalize the state so any modifications are written to the trie
root, err := statedb.CommitTo(db, true)
if err != nil {
return nil, err
}
if err := statedb.Reset(root); err != nil {
return nil, err
}
// After every N blocks, prune the database to only retain relevant data
if block.NumberU64()%4096 == 0 || block.NumberU64() == origin {
var (
nodes = db.memdb.Len()
begin = time.Now()
)
db.Prune(root)
log.Info("Pruned tracer state entries", "deleted", nodes-db.memdb.Len(), "left", db.memdb.Len(), "elapsed", time.Since(begin))
statedb, _ = state.New(root, state.NewDatabase(db))
}
}
log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start))
return statedb, nil
}
// 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, hash common.Hash, config *TraceConfig) (interface{}, error) {
// Retrieve the transaction and assemble its EVM context
tx, blockHash, _, index := core.GetTransaction(api.eth.ChainDb(), hash)
if tx == nil {
return nil, fmt.Errorf("transaction %x not found", hash)
}
reexec := defaultTraceReexec
if config.Reexec != nil {
reexec = *config.Reexec
}
msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec)
if err != nil {
return nil, err
}
// Trace the transaction and return
return api.traceTx(ctx, msg, vmctx, statedb, config)
}
// traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will
// be tracer dependent.
func (api *PrivateDebugAPI) traceTx(ctx context.Context, message core.Message, vmctx vm.Context, statedb *state.StateDB, config *TraceConfig) (interface{}, error) {
// Assemble the structured logger or the JavaScript tracer
var (
tracer vm.Tracer
err error
)
switch {
case config != nil && config.Tracer != nil:
// Define a meaningful timeout of a single transaction trace
timeout := defaultTraceTimeout
if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
return nil, err
}
}
// Constuct the JavaScript tracer to execute with
if tracer, err = tracers.New(*config.Tracer); err != nil {
return nil, err
}
// Handle timeouts and RPC cancellations
deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
go func() {
<-deadlineCtx.Done()
tracer.(*tracers.Tracer).Stop(errors.New("execution timeout"))
}()
defer cancel()
case config == nil:
tracer = vm.NewStructLogger(nil)
default:
tracer = vm.NewStructLogger(config.LogConfig)
}
// Run the transaction with tracing enabled.
vmenv := vm.NewEVM(vmctx, statedb, api.config, vm.Config{Debug: true, Tracer: tracer})
ret, gas, failed, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()))
if err != nil {
return nil, fmt.Errorf("tracing failed: %v", err)
}
// Depending on the tracer type, format and return the output
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 *tracers.Tracer:
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, reexec uint64) (core.Message, vm.Context, *state.StateDB, error) {
// Create the parent state database
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("parent %x not found", block.ParentHash())
}
statedb, err := api.computeStateDB(parent, reexec)
if err != nil {
return nil, vm.Context{}, nil, err
}
// Recompute transactions up to the target index.
signer := types.MakeSigner(api.config, block.Number())
for idx, tx := range block.Transactions() {
// Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer)
context := core.NewEVMContext(msg, block.Header(), api.eth.blockchain, nil)
if idx == txIndex {
return msg, context, statedb, nil
}
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{})
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
}
statedb.DeleteSuicides()
}
return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash)
}

View file

@ -26,6 +26,7 @@ import (
"time" "time"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/consensus/ethash"
"github.com/EthereumCommonwealth/go-callisto/core" "github.com/EthereumCommonwealth/go-callisto/core"
"github.com/EthereumCommonwealth/go-callisto/core/state" "github.com/EthereumCommonwealth/go-callisto/core/state"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
@ -107,7 +108,7 @@ func newTester() *downloadTester {
// reassembly. // reassembly.
func (dl *downloadTester) makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) { func (dl *downloadTester) makeChain(n int, seed byte, parent *types.Block, parentReceipts types.Receipts, heavy bool) ([]common.Hash, map[common.Hash]*types.Header, map[common.Hash]*types.Block, map[common.Hash]types.Receipts) {
// Generate the block chain // Generate the block chain
blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, dl.peerDb, n, func(i int, block *core.BlockGen) { blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), dl.peerDb, n, func(i int, block *core.BlockGen) {
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// If a heavy chain is requested, delay blocks to raise difficulty // If a heavy chain is requested, delay blocks to raise difficulty

View file

@ -25,6 +25,7 @@ import (
"time" "time"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/consensus/ethash"
"github.com/EthereumCommonwealth/go-callisto/core" "github.com/EthereumCommonwealth/go-callisto/core"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
"github.com/EthereumCommonwealth/go-callisto/crypto" "github.com/EthereumCommonwealth/go-callisto/crypto"
@ -45,7 +46,7 @@ var (
// contains a transaction and every 5th an uncle to allow testing correct block // contains a transaction and every 5th an uncle to allow testing correct block
// reassembly. // reassembly.
func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) {
blocks, _ := core.GenerateChain(params.TestChainConfig, parent, testdb, n, func(i int, block *core.BlockGen) { blocks, _ := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) {
block.SetCoinbase(common.Address{seed}) block.SetCoinbase(common.Address{seed})
// If the block number is multiple of 3, send a bonus transaction to the miner // If the block number is multiple of 3, send a bonus transaction to the miner

View file

@ -26,6 +26,7 @@ import (
"time" "time"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/consensus/ethash"
"github.com/EthereumCommonwealth/go-callisto/core" "github.com/EthereumCommonwealth/go-callisto/core"
"github.com/EthereumCommonwealth/go-callisto/core/bloombits" "github.com/EthereumCommonwealth/go-callisto/core/bloombits"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
@ -136,7 +137,7 @@ func TestBlockSubscription(t *testing.T) {
backend = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} backend = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed}
api = NewPublicFilterAPI(backend, false) api = NewPublicFilterAPI(backend, false)
genesis = new(core.Genesis).MustCommit(db) genesis = new(core.Genesis).MustCommit(db)
chain, _ = core.GenerateChain(params.TestChainConfig, genesis, db, 10, func(i int, gen *core.BlockGen) {}) chain, _ = core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 10, func(i int, gen *core.BlockGen) {})
chainEvents = []core.ChainEvent{} chainEvents = []core.ChainEvent{}
) )

View file

@ -24,6 +24,7 @@ import (
"testing" "testing"
"github.com/EthereumCommonwealth/go-callisto/common" "github.com/EthereumCommonwealth/go-callisto/common"
"github.com/EthereumCommonwealth/go-callisto/consensus/ethash"
"github.com/EthereumCommonwealth/go-callisto/core" "github.com/EthereumCommonwealth/go-callisto/core"
"github.com/EthereumCommonwealth/go-callisto/core/types" "github.com/EthereumCommonwealth/go-callisto/core/types"
"github.com/EthereumCommonwealth/go-callisto/crypto" "github.com/EthereumCommonwealth/go-callisto/crypto"
@ -65,7 +66,7 @@ func BenchmarkFilters(b *testing.B) {
defer db.Close() defer db.Close()
genesis := core.GenesisBlockForTesting(db, addr1, big.NewInt(1000000)) genesis := core.GenesisBlockForTesting(db, addr1, big.NewInt(1000000))
chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, db, 100010, func(i int, gen *core.BlockGen) { chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 100010, func(i int, gen *core.BlockGen) {
switch i { switch i {
case 2403: case 2403:
receipt := makeReceipt(addr1) receipt := makeReceipt(addr1)
@ -132,7 +133,7 @@ func TestFilters(t *testing.T) {
defer db.Close() defer db.Close()
genesis := core.GenesisBlockForTesting(db, addr, big.NewInt(1000000)) genesis := core.GenesisBlockForTesting(db, addr, big.NewInt(1000000))
chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, db, 1000, func(i int, gen *core.BlockGen) { chain, receipts := core.GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) {
switch i { switch i {
case 1: case 1:
receipt := types.NewReceipt(nil, false, new(big.Int)) receipt := types.NewReceipt(nil, false, new(big.Int))

View file

@ -747,10 +747,11 @@ func (self *ProtocolManager) txBroadcastLoop() {
// EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known // EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known
// about the host peer. // about the host peer.
type EthNodeInfo struct { type EthNodeInfo struct {
Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3) Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3)
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block
} }
// NodeInfo retrieves some protocol metadata about the running host node. // NodeInfo retrieves some protocol metadata about the running host node.
@ -760,6 +761,7 @@ func (self *ProtocolManager) NodeInfo() *EthNodeInfo {
Network: self.networkId, Network: self.networkId,
Difficulty: self.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()), Difficulty: self.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()),
Genesis: self.blockchain.Genesis().Hash(), Genesis: self.blockchain.Genesis().Hash(),
Config: self.blockchain.Config(),
Head: currentBlock.Hash(), Head: currentBlock.Hash(),
} }
} }

View file

@ -498,7 +498,7 @@ func testDAOChallenge(t *testing.T, localForked, remoteForked bool, timeout bool
} }
// Create a block to reply to the challenge if no timeout is simulated // Create a block to reply to the challenge if no timeout is simulated
if !timeout { if !timeout {
blocks, _ := core.GenerateChain(&params.ChainConfig{}, genesis, db, 1, func(i int, block *core.BlockGen) { blocks, _ := core.GenerateChain(&params.ChainConfig{}, genesis, ethash.NewFaker(), db, 1, func(i int, block *core.BlockGen) {
if remoteForked { if remoteForked {
block.SetExtra(params.DAOForkBlockExtra) block.SetExtra(params.DAOForkBlockExtra)
} }

View file

@ -61,7 +61,7 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func
genesis = gspec.MustCommit(db) genesis = gspec.MustCommit(db)
blockchain, _ = core.NewBlockChain(db, gspec.Config, engine, vm.Config{}) blockchain, _ = core.NewBlockChain(db, gspec.Config, engine, vm.Config{})
) )
chain, _ := core.GenerateChain(gspec.Config, genesis, db, blocks, generator) chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator)
if _, err := blockchain.InsertChain(chain); err != nil { if _, err := blockchain.InsertChain(chain); err != nil {
panic(err) panic(err)
} }

View file

@ -0,0 +1,86 @@
// 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/>.
// 4byteTracer searches for 4byte-identifiers, and collects them for post-processing.
// It collects the methods identifiers along with the size of the supplied data, so
// a reversed signature can be matched against the size of the data.
//
// Example:
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
// {
// 0x27dc297e-128: 1,
// 0x38cc4831-0: 2,
// 0x524f3889-96: 1,
// 0xadf59f99-288: 1,
// 0xc281d19e-0: 1
// }
{
// ids aggregates the 4byte ids found.
ids : {},
// callType returns 'false' for non-calls, or the peek-index for the first param
// after 'value', i.e. meminstart.
callType: function(opstr){
switch(opstr){
case "CALL": case "CALLCODE":
// gas, addr, val, memin, meminsz, memout, memoutsz
return 3; // stack ptr to memin
case "DELEGATECALL": case "STATICCALL":
// gas, addr, memin, meminsz, memout, memoutsz
return 2; // stack ptr to memin
}
return false;
},
// store save the given indentifier and datasize.
store: function(id, size){
var key = "" + toHex(id) + "-" + size;
this.ids[key] = this.ids[key] + 1 || 1;
},
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
// Skip any opcodes that are not internal calls
var ct = this.callType(log.op.toString());
if (!ct) {
return;
}
// Skip any pre-compile invocations, those are just fancy opcodes
if (isPrecompiled(toAddress(log.stack.peek(1)))) {
return;
}
// Gather internal call details
var inSz = log.stack.peek(ct + 1).valueOf();
if (inSz >= 4) {
var inOff = log.stack.peek(ct).valueOf();
this.store(log.memory.slice(inOff, inOff + 4), inSz-4);
}
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) { },
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx) {
// Save the outer calldata also
if (ctx.input.length > 4) {
this.store(slice(ctx.input, 0, 4), ctx.input.length-4)
}
return this.ids;
},
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,246 @@
// 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/>.
// callTracer is a full blown transaction tracer that extracts and reports all
// the internal calls made by a transaction, along with any useful information.
{
// callstack is the current recursive call stack of the EVM execution.
callstack: [{}],
// descended tracks whether we've just descended from an outer transaction into
// an inner call.
descended: false,
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
// Capture any errors immediately
var error = log.getError();
if (error !== undefined) {
this.fault(log, db);
return;
}
// We only care about system opcodes, faster if we pre-check once
var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
if (syscall) {
var op = log.op.toString();
}
// If a new contract is being created, add to the call stack
if (syscall && op == 'CREATE') {
var inOff = log.stack.peek(1).valueOf();
var inEnd = inOff + log.stack.peek(2).valueOf();
// Assemble the internal call report and store for completion
var call = {
type: op,
from: toHex(log.contract.getAddress()),
input: toHex(log.memory.slice(inOff, inEnd)),
gasIn: log.getGas(),
gasCost: log.getCost(),
value: '0x' + log.stack.peek(0).toString(16)
};
this.callstack.push(call);
this.descended = true
return;
}
// If a contract is being self destructed, gather that as a subcall too
if (syscall && op == 'SELFDESTRUCT') {
var left = this.callstack.length;
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push({type: op});
return
}
// If a new method invocation is being done, add to the call stack
if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) {
// Skip any pre-compile invocations, those are just fancy opcodes
var to = toAddress(log.stack.peek(1).toString(16));
if (isPrecompiled(to)) {
return
}
var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1);
var inOff = log.stack.peek(2 + off).valueOf();
var inEnd = inOff + log.stack.peek(3 + off).valueOf();
// Assemble the internal call report and store for completion
var call = {
type: op,
from: toHex(log.contract.getAddress()),
to: toHex(to),
input: toHex(log.memory.slice(inOff, inEnd)),
gasIn: log.getGas(),
gasCost: log.getCost(),
outOff: log.stack.peek(4 + off).valueOf(),
outLen: log.stack.peek(5 + off).valueOf()
};
if (op != 'DELEGATECALL' && op != 'STATICCALL') {
call.value = '0x' + log.stack.peek(2).toString(16);
}
this.callstack.push(call);
this.descended = true
return;
}
// If we've just descended into an inner call, retrieve it's true allowance. We
// need to extract if from within the call as there may be funky gas dynamics
// with regard to requested and actually given gas (2300 stipend, 63/64 rule).
if (this.descended) {
if (log.getDepth() >= this.callstack.length) {
this.callstack[this.callstack.length - 1].gas = log.getGas();
} else {
// TODO(karalabe): The call was made to a plain account. We currently don't
// have access to the true gas amount inside the call and so any amount will
// mostly be wrong since it depends on a lot of input args. Skip gas for now.
}
this.descended = false;
}
// If an existing call is returning, pop off the call stack
if (syscall && op == 'REVERT') {
this.callstack[this.callstack.length - 1].error = "execution reverted";
return;
}
if (log.getDepth() == this.callstack.length - 1) {
// Pop off the last call and get the execution results
var call = this.callstack.pop();
if (call.type == 'CREATE') {
// If the call was a CREATE, retrieve the contract address and output code
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16);
delete call.gasIn; delete call.gasCost;
var ret = log.stack.peek(0);
if (!ret.equals(0)) {
call.to = toHex(toAddress(ret.toString(16)));
call.output = toHex(db.getCode(toAddress(ret.toString(16))));
} else if (call.error === undefined) {
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
}
} else {
// If the call was a contract call, retrieve the gas usage and output
if (call.gas !== undefined) {
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
var ret = log.stack.peek(0);
if (!ret.equals(0)) {
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
} else if (call.error === undefined) {
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
}
}
delete call.gasIn; delete call.gasCost;
delete call.outOff; delete call.outLen;
}
if (call.gas !== undefined) {
call.gas = '0x' + bigInt(call.gas).toString(16);
}
// Inject the call into the previous one
var left = this.callstack.length;
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push(call);
}
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {
// If the topmost call already reverted, don't handle the additional fault again
if (this.callstack[this.callstack.length - 1].error !== undefined) {
return;
}
// Pop off the just failed call
var call = this.callstack.pop();
call.error = log.getError();
// Consume all available gas and clean any leftovers
if (call.gas !== undefined) {
call.gas = '0x' + bigInt(call.gas).toString(16);
call.gasUsed = call.gas
}
delete call.gasIn; delete call.gasCost;
delete call.outOff; delete call.outLen;
// Flatten the failed call into its parent
var left = this.callstack.length;
if (left > 0) {
if (this.callstack[left-1].calls === undefined) {
this.callstack[left-1].calls = [];
}
this.callstack[left-1].calls.push(call);
return;
}
// Last call failed too, leave it in the stack
this.callstack.push(call);
},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx, db) {
var result = {
type: ctx.type,
from: toHex(ctx.from),
to: toHex(ctx.to),
value: '0x' + ctx.value.toString(16),
gas: '0x' + bigInt(ctx.gas).toString(16),
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
input: toHex(ctx.input),
output: toHex(ctx.output),
time: ctx.time,
};
if (this.callstack[0].calls !== undefined) {
result.calls = this.callstack[0].calls;
}
if (this.callstack[0].error !== undefined) {
result.error = this.callstack[0].error;
} else if (ctx.error !== undefined) {
result.error = ctx.error;
}
if (result.error !== undefined) {
delete result.output;
}
return this.finalize(result);
},
// finalize recreates a call object using the final desired field oder for json
// serialization. This is a nicety feature to pass meaningfully ordered results
// to users who don't interpret it, just display it.
finalize: function(call) {
var sorted = {
type: call.type,
from: call.from,
to: call.to,
value: call.value,
gas: call.gas,
gasUsed: call.gasUsed,
input: call.input,
output: call.output,
error: call.error,
time: call.time,
calls: call.calls,
}
for (var key in sorted) {
if (sorted[key] === undefined) {
delete sorted[key];
}
}
if (sorted.calls !== undefined) {
for (var i=0; i<sorted.calls.length; i++) {
sorted.calls[i] = this.finalize(sorted.calls[i]);
}
}
return sorted;
}
}

View file

@ -0,0 +1,93 @@
// 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/>.
// evmdisTracer returns sufficent information from a trace to perform evmdis-style
// disassembly.
{
stack: [{ops: []}],
npushes: {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 32: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 0, 56: 1, 57: 0, 58: 1, 59: 1, 60: 0, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 80: 0, 81: 1, 82: 0, 83: 0, 84: 1, 85: 0, 86: 0, 87: 0, 88: 1, 89: 1, 90: 1, 91: 0, 96: 1, 97: 1, 98: 1, 99: 1, 100: 1, 101: 1, 102: 1, 103: 1, 104: 1, 105: 1, 106: 1, 107: 1, 108: 1, 109: 1, 110: 1, 111: 1, 112: 1, 113: 1, 114: 1, 115: 1, 116: 1, 117: 1, 118: 1, 119: 1, 120: 1, 121: 1, 122: 1, 123: 1, 124: 1, 125: 1, 126: 1, 127: 1, 128: 2, 129: 3, 130: 4, 131: 5, 132: 6, 133: 7, 134: 8, 135: 9, 136: 10, 137: 11, 138: 12, 139: 13, 140: 14, 141: 15, 142: 16, 143: 17, 144: 2, 145: 3, 146: 4, 147: 5, 148: 6, 149: 7, 150: 8, 151: 9, 152: 10, 153: 11, 154: 12, 155: 13, 156: 14, 157: 15, 158: 16, 159: 17, 160: 0, 161: 0, 162: 0, 163: 0, 164: 0, 240: 1, 241: 1, 242: 1, 243: 0, 244: 0, 255: 0},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function() { return this.stack[0].ops; },
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) { },
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
var frame = this.stack[this.stack.length - 1];
var error = log.getError();
if (error) {
frame["error"] = error;
} else if (log.getDepth() == this.stack.length) {
opinfo = {
op: log.op.toNumber(),
depth : log.getDepth(),
result: [],
};
if (frame.ops.length > 0) {
var prevop = frame.ops[frame.ops.length - 1];
for(var i = 0; i < this.npushes[prevop.op]; i++)
prevop.result.push(log.stack.peek(i).toString(16));
}
switch(log.op.toString()) {
case "CALL": case "CALLCODE":
var instart = log.stack.peek(3).valueOf();
var insize = log.stack.peek(4).valueOf();
opinfo["gas"] = log.stack.peek(0).valueOf();
opinfo["to"] = log.stack.peek(1).toString(16);
opinfo["value"] = log.stack.peek(2).toString();
opinfo["input"] = log.memory.slice(instart, instart + insize);
opinfo["error"] = null;
opinfo["return"] = null;
opinfo["ops"] = [];
this.stack.push(opinfo);
break;
case "DELEGATECALL": case "STATICCALL":
var instart = log.stack.peek(2).valueOf();
var insize = log.stack.peek(3).valueOf();
opinfo["op"] = log.op.toString();
opinfo["gas"] = log.stack.peek(0).valueOf();
opinfo["to"] = log.stack.peek(1).toString(16);
opinfo["input"] = log.memory.slice(instart, instart + insize);
opinfo["error"] = null;
opinfo["return"] = null;
opinfo["ops"] = [];
this.stack.push(opinfo);
break;
case "RETURN":
var out = log.stack.peek(0).valueOf();
var outsize = log.stack.peek(1).valueOf();
frame.return = log.memory.slice(out, out + outsize);
break;
case "STOP": case "SUICIDE":
frame.return = log.memory.slice(0, 0);
break;
case "JUMPDEST":
opinfo["pc"] = log.getPC();
}
if(log.op.isPush()) {
opinfo["len"] = log.op.toNumber() - 0x5e;
}
frame.ops.push(opinfo);
} else {
this.stack = this.stack.slice(0, log.getDepth());
}
}
}

View file

@ -0,0 +1,29 @@
// 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/>.
// noopTracer is just the barebone boilerplate code required from a JavaScript
// object to be usable as a transaction tracer.
{
// step is invoked for every opcode that the VM executes.
step: function(log, db) { },
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) { },
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx, db) { }
}

View file

@ -0,0 +1,32 @@
// 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/>.
// opcountTracer is a sample tracer that just counts the number of instructions
// executed by the EVM before the transaction terminated.
{
// count tracks the number of EVM instructions executed.
count: 0,
// step is invoked for every opcode that the VM executes.
step: function(log, db) { this.count++ },
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) { },
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx, db) { return this.count }
}

View file

@ -0,0 +1,103 @@
// 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/>.
// prestateTracer outputs sufficient information to create a local execution of
// the transaction from a custom assembled genesis block.
{
// prestate is the genesis that we're building.
prestate: null,
// lookupAccount injects the specified account into the prestate object.
lookupAccount: function(addr, db){
var acc = toHex(addr);
if (this.prestate[acc] === undefined) {
this.prestate[acc] = {
balance: '0x' + db.getBalance(addr).toString(16),
nonce: db.getNonce(addr),
code: toHex(db.getCode(addr)),
storage: {}
};
}
},
// lookupStorage injects the specified storage entry of the given account into
// the prestate object.
lookupStorage: function(addr, key, db){
var acc = toHex(addr);
var idx = toHex(key);
if (this.prestate[acc].storage[idx] === undefined) {
var val = toHex(db.getState(addr, key));
if (val != "0x0000000000000000000000000000000000000000000000000000000000000000") {
this.prestate[acc].storage[idx] = toHex(db.getState(addr, key));
}
}
},
// result is invoked when all the opcodes have been iterated over and returns
// the final result of the tracing.
result: function(ctx, db) {
// At this point, we need to deduct the 'value' from the
// outer transaction, and move it back to the origin
this.lookupAccount(ctx.from, db);
var fromBal = bigInt(this.prestate[toHex(ctx.from)].balance.slice(2), 16);
var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16);
this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16);
this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).toString(16);
// Decrement the caller's nonce, and remove empty create targets
this.prestate[toHex(ctx.from)].nonce--;
if (ctx.type == 'CREATE') {
// We can blibdly delete the contract prestate, as any existing state would
// have caused the transaction to be rejected as invalid in the first place.
delete this.prestate[toHex(ctx.to)];
}
// Return the assembled allocations (prestate)
return this.prestate;
},
// step is invoked for every opcode that the VM executes.
step: function(log, db) {
// Add the current account if we just started tracing
if (this.prestate === null){
this.prestate = {};
// Balance will potentially be wrong here, since this will include the value
// sent along with the message. We fix that in 'result()'.
this.lookupAccount(log.contract.getAddress(), db);
}
// Whenever new state is accessed, add it to the prestate
switch (log.op.toString()) {
case "EXTCODECOPY": case "EXTCODESIZE": case "BALANCE":
this.lookupAccount(toAddress(log.stack.peek(0).toString(16)), db);
break;
case "CREATE":
var from = log.contract.getAddress();
this.lookupAccount(toContract(from, db.getNonce(from)), db);
break;
case "CALL": case "CALLCODE": case "DELEGATECALL": case "STATICCALL":
this.lookupAccount(toAddress(log.stack.peek(1).toString(16)), db);
break;
case 'SSTORE':case 'SLOAD':
this.lookupStorage(log.contract.getAddress(), toWord(log.stack.peek(0).toString(16)), db);
break;
}
},
// fault is invoked when the actual execution of an opcode fails.
fault: function(log, db) {}
}

View file

@ -0,0 +1,21 @@
// 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/>.
//go:generate go-bindata -nometadata -o assets.go -pkg tracers -ignore ((tracers)|(assets)).go ./...
//go:generate gofmt -s -w assets.go
// Package tracers contains the actual JavaScript tracer assets.
package tracers

View file

@ -0,0 +1,58 @@
{
"context": {
"difficulty": "3755480783",
"gasLimit": "5401723",
"miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511",
"number": "2294702",
"timestamp": "1513676146"
},
"genesis": {
"alloc": {
"0x13e4acefe6a6700604929946e70e6443e4e73447": {
"balance": "0xcf3e0938579f000",
"code": "0x",
"nonce": "9",
"storage": {}
},
"0x7dc9c9730689ff0b0fd506c67db815f12d90a448": {
"balance": "0x0",
"code": "0x",
"nonce": "0",
"storage": {}
}
},
"config": {
"byzantiumBlock": 1700000,
"chainId": 3,
"daoForkSupport": true,
"eip150Block": 0,
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
"eip155Block": 10,
"eip158Block": 10,
"ethash": {},
"homesteadBlock": 0
},
"difficulty": "3757315409",
"extraData": "0x566961425443",
"gasLimit": "5406414",
"hash": "0xae107f592eebdd9ff8d6ba00363676096e6afb0e1007a7d3d0af88173077378d",
"miner": "0xd049bfd667cb46aa3ef5df0da3e57db3be39e511",
"mixHash": "0xc927aa05a38bc3de864e95c33b3ae559d3f39c4ccd51cef6f113f9c50ba0caf1",
"nonce": "0x93363bbd2c95f410",
"number": "2294701",
"stateRoot": "0x6b6737d5bde8058990483e915866bd1578014baeff57bd5e4ed228a2bfad635c",
"timestamp": "1513676127",
"totalDifficulty": "7160808139332585"
},
"input": "0xf907ef098504e3b29200830897be8080b9079c606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a1129a01060f46676a5dff6f407f0f51eb6f37f5c8c54e238c70221e18e65fc29d3ea65a0557b01c50ff4ffaac8ed6e5d31237a4ecbac843ab1bfe8bb0165a0060df7c54f",
"result": {
"from": "0x13e4acefe6a6700604929946e70e6443e4e73447",
"gas": "0x5e106",
"gasUsed": "0x5e106",
"input": "0x606060405260405160208061077c83398101604052808051906020019091905050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415151561007d57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506001600460006101000a81548160ff02191690831515021790555050610653806101296000396000f300606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029000000000000000000000000c65e620a3a55451316168d57e268f5702ef56a11",
"output": "0x606060405260043610610083576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806305e4382a146100855780631c02708d146100ae5780632e1a7d4d146100c35780635114cb52146100e6578063a37dda2c146100fe578063ae200e7914610153578063b5769f70146101a8575b005b341561009057600080fd5b6100986101d1565b6040518082815260200191505060405180910390f35b34156100b957600080fd5b6100c16101d7565b005b34156100ce57600080fd5b6100e460048080359060200190919050506102eb565b005b6100fc6004808035906020019091905050610513565b005b341561010957600080fd5b6101116105d6565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561015e57600080fd5b6101666105fc565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156101b357600080fd5b6101bb610621565b6040518082815260200191505060405180910390f35b60025481565b60011515600460009054906101000a900460ff1615151415156101f957600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102a15750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b15156102ac57600080fd5b6000600460006101000a81548160ff0219169083151502179055506003543073ffffffffffffffffffffffffffffffffffffffff163103600281905550565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806103935750600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16145b151561039e57600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141561048357600060025411801561040757506002548111155b151561041257600080fd5b80600254036002819055506000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561047e57600080fd5b610510565b600060035411801561049757506003548111155b15156104a257600080fd5b8060035403600381905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f19350505050151561050f57600080fd5b5b50565b60011515600460009054906101000a900460ff16151514151561053557600080fd5b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614801561059657506003548160035401115b80156105bd575080600354013073ffffffffffffffffffffffffffffffffffffffff163110155b15156105c857600080fd5b806003540160038190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600354815600a165627a7a72305820c3b849e8440987ce43eae3097b77672a69234d516351368b03fe5b7de03807910029",
"to": "0x7dc9c9730689ff0b0fd506c67db815f12d90a448",
"type": "CREATE",
"value": "0x0"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,58 @@
{
"context": {
"difficulty": "3665057456",
"gasLimit": "5232723",
"miner": "0xf4d8e706cfb25c0decbbdd4d2e2cc10c66376a3f",
"number": "2294501",
"timestamp": "1513673601"
},
"genesis": {
"alloc": {
"0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9": {
"balance": "0x2a3fc32bcc019283",
"code": "0x",
"nonce": "10",
"storage": {}
},
"0xabbcd5b340c80b5f1c0545c04c987b87310296ae": {
"balance": "0x0",
"code": "0x606060405236156100755763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416632d0335ab811461007a578063548db174146100ab5780637f649783146100fc578063b092145e1461014d578063c3f44c0a14610186578063c47cf5de14610203575b600080fd5b341561008557600080fd5b610099600160a060020a0360043516610270565b60405190815260200160405180910390f35b34156100b657600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061028f95505050505050565b005b341561010757600080fd5b6100fa600460248135818101908301358060208181020160405190810160405280939291908181526020018383602002808284375094965061029e95505050505050565b005b341561015857600080fd5b610172600160a060020a03600435811690602435166102ad565b604051901515815260200160405180910390f35b341561019157600080fd5b6100fa6004803560ff1690602480359160443591606435600160a060020a0316919060a49060843590810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965050509235600160a060020a031692506102cd915050565b005b341561020e57600080fd5b61025460046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061056a95505050505050565b604051600160a060020a03909116815260200160405180910390f35b600160a060020a0381166000908152602081905260409020545b919050565b61029a816000610594565b5b50565b61029a816001610594565b5b50565b600160209081526000928352604080842090915290825290205460ff1681565b60008080600160a060020a038416158061030d5750600160a060020a038085166000908152600160209081526040808320339094168352929052205460ff165b151561031857600080fd5b6103218561056a565b600160a060020a038116600090815260208190526040808220549295507f19000000000000000000000000000000000000000000000000000000000000009230918891908b908b90517fff000000000000000000000000000000000000000000000000000000000000008089168252871660018201526c01000000000000000000000000600160a060020a038088168202600284015286811682026016840152602a8301869052841602604a820152605e810182805190602001908083835b6020831061040057805182525b601f1990920191602091820191016103e0565b6001836020036101000a0380198251168184511617909252505050919091019850604097505050505050505051809103902091506001828a8a8a6040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f1151561049957600080fd5b5050602060405103519050600160a060020a03838116908216146104bc57600080fd5b600160a060020a0380841660009081526020819052604090819020805460010190559087169086905180828051906020019080838360005b8381101561050d5780820151818401525b6020016104f4565b50505050905090810190601f16801561053a5780820380516001836020036101000a031916815260200191505b5091505060006040518083038160008661646e5a03f1915050151561055e57600080fd5b5b505050505050505050565b600060248251101561057e5750600061028a565b600160a060020a0360248301511690505b919050565b60005b825181101561060157600160a060020a033316600090815260016020526040812083918584815181106105c657fe5b90602001906020020151600160a060020a031681526020810191909152604001600020805460ff19169115159190911790555b600101610597565b5b5050505600a165627a7a723058200027e8b695e9d2dea9f3629519022a69f3a1d23055ce86406e686ea54f31ee9c0029",
"nonce": "1",
"storage": {}
}
},
"config": {
"byzantiumBlock": 1700000,
"chainId": 3,
"daoForkSupport": true,
"eip150Block": 0,
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
"eip155Block": 10,
"eip158Block": 10,
"ethash": {},
"homesteadBlock": 0
},
"difficulty": "3672229776",
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
"gasLimit": "5227619",
"hash": "0xa07b3d6c6bf63f5f981016db9f2d1d93033833f2c17e8bf7209e85f1faf08076",
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
"mixHash": "0x806e151ce2817be922e93e8d5921fa0f0d0fd213d6b2b9a3fa17458e74a163d0",
"nonce": "0xbc5d43adc2c30c7d",
"number": "2294500",
"stateRoot": "0xca645b335888352ef9d8b1ef083e9019648180b259026572e3139717270de97d",
"timestamp": "1513673552",
"totalDifficulty": "7160066586979149"
},
"input": "0xf9018b0a8505d21dba00832dc6c094abbcd5b340c80b5f1c0545c04c987b87310296ae80b9012473b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988000000000000000000000000000000000000000000000000000000000000000000000000000000001ba0fd659d76a4edbd2a823e324c93f78ad6803b30ff4a9c8bce71ba82798975c70ca06571eecc0b765688ec6c78942c5ee8b585e00988c0141b518287e9be919bc48a",
"result": {
"error": "execution reverted",
"from": "0x0f6cef2b7fbb504782e35aa82a2207e816a2b7a9",
"gas": "0x2d55e8",
"gasUsed": "0xc3",
"input": "0x73b40a5c000000000000000000000000400de2e016bda6577407dfc379faba9899bc73ef0000000000000000000000002cc31912b2b0f3075a87b3640923d45a26cef3ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064d79d8e6c7265636f76657279416464726573730000000000000000000000000000000000000000000000000000000000383e3ec32dc0f66d8fe60dbdc2f6815bdf73a988383e3ec32dc0f66d8fe60dbdc2f6815bdf73a98800000000000000000000000000000000000000000000000000000000000000000000000000000000",
"to": "0xabbcd5b340c80b5f1c0545c04c987b87310296ae",
"type": "CALL",
"value": "0x0"
}
}

View file

@ -0,0 +1,78 @@
{
"context": {
"difficulty": "3502894804",
"gasLimit": "4722976",
"miner": "0x1585936b53834b021f68cc13eeefdec2efc8e724",
"number": "2289806",
"timestamp": "1513601314"
},
"genesis": {
"alloc": {
"0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
"balance": "0x0",
"code": "0x",
"nonce": "22",
"storage": {}
},
"0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
"balance": "0x4d87094125a369d9bd5",
"code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029",
"nonce": "1",
"storage": {
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb",
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000",
"0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c",
"0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834"
}
},
"0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
"balance": "0x1780d77678137ac1b775",
"code": "0x",
"nonce": "29072",
"storage": {}
}
},
"config": {
"byzantiumBlock": 1700000,
"chainId": 3,
"daoForkSupport": true,
"eip150Block": 0,
"eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d",
"eip155Block": 10,
"eip158Block": 10,
"ethash": {},
"homesteadBlock": 0
},
"difficulty": "3509749784",
"extraData": "0x4554482e45544846414e532e4f52472d4641313738394444",
"gasLimit": "4727564",
"hash": "0x609948ac3bd3c00b7736b933248891d6c901ee28f066241bddb28f4e00a9f440",
"miner": "0xbbf5029fd710d227630c8b7d338051b8e76d50b3",
"mixHash": "0xb131e4507c93c7377de00e7c271bf409ec7492767142ff0f45c882f8068c2ada",
"nonce": "0x4eb12e19c16d43da",
"number": "2289805",
"stateRoot": "0xc7f10f352bff82fac3c2999d3085093d12652e19c7fd32591de49dc5d91b4f1f",
"timestamp": "1513601261",
"totalDifficulty": "7143276353481064"
},
"input": "0xf88b8271908506fc23ac0083015f90943b873a919aa0512d5a0f09e6dcceaa4a6727fafe80a463e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c52aa0bdce0b59e8761854e857fe64015f06dd08a4fbb7624f6094893a79a72e6ad6bea01d9dde033cff7bb235a3163f348a6d7ab8d6b52bc0963a95b91612e40ca766a4",
"result": {
"calls": [
{
"from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
"input": "0x",
"to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
"type": "CALL",
"value": "0x6f05b59d3b20000"
}
],
"from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
"gas": "0x10738",
"gasUsed": "0x3ef9",
"input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
"output": "0x0000000000000000000000000000000000000000000000000000000000000001",
"to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
"type": "CALL",
"value": "0x0"
}
}

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