mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 09:23:48 +00:00
Upgrade Callisto client from upstream
This commit is contained in:
commit
e628f04131
165 changed files with 161500 additions and 1658 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -35,6 +35,7 @@ profile.cov
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
# dashboard
|
# dashboard
|
||||||
|
/dashboard/assets/flow-typed
|
||||||
/dashboard/assets/node_modules
|
/dashboard/assets/node_modules
|
||||||
/dashboard/assets/stats.json
|
/dashboard/assets/stats.json
|
||||||
/dashboard/assets/public/bundle.js
|
/dashboard/assets/public/bundle.js
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package abi
|
package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -50,51 +51,47 @@ func JSON(reader io.Reader) (ABI, error) {
|
||||||
// methods string signature. (signature = baz(uint32,string32))
|
// methods string signature. (signature = baz(uint32,string32))
|
||||||
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
// Fetch the ABI of the requested method
|
// Fetch the ABI of the requested method
|
||||||
var method Method
|
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
method = abi.Constructor
|
// constructor
|
||||||
} else {
|
arguments, err := abi.Constructor.Inputs.Pack(args...)
|
||||||
m, exist := abi.Methods[name]
|
if err != nil {
|
||||||
if !exist {
|
return nil, err
|
||||||
return nil, fmt.Errorf("method '%s' not found", name)
|
|
||||||
}
|
}
|
||||||
method = m
|
return arguments, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
arguments, err := method.pack(args...)
|
method, exist := abi.Methods[name]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("method '%s' not found", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments, err := method.Inputs.Pack(args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Pack up the method ID too if not a constructor and return
|
// Pack up the method ID too if not a constructor and return
|
||||||
if name == "" {
|
|
||||||
return arguments, nil
|
|
||||||
}
|
|
||||||
return append(method.Id(), arguments...), nil
|
return append(method.Id(), arguments...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack output in v according to the abi specification
|
// Unpack output in v according to the abi specification
|
||||||
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||||
if err = bytesAreProper(output); err != nil {
|
if len(output) == 0 {
|
||||||
return err
|
return fmt.Errorf("abi: unmarshalling empty output")
|
||||||
}
|
}
|
||||||
// since there can't be naming collisions with contracts and events,
|
// since there can't be naming collisions with contracts and events,
|
||||||
// we need to decide whether we're calling a method or an event
|
// we need to decide whether we're calling a method or an event
|
||||||
var unpack unpacker
|
|
||||||
if method, ok := abi.Methods[name]; ok {
|
if method, ok := abi.Methods[name]; ok {
|
||||||
unpack = method
|
if len(output)%32 != 0 {
|
||||||
|
return fmt.Errorf("abi: improperly formatted output")
|
||||||
|
}
|
||||||
|
return method.Outputs.Unpack(v, output)
|
||||||
} else if event, ok := abi.Events[name]; ok {
|
} else if event, ok := abi.Events[name]; ok {
|
||||||
unpack = event
|
return event.Inputs.Unpack(v, output)
|
||||||
} else {
|
|
||||||
return fmt.Errorf("abi: could not locate named method or event.")
|
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("abi: could not locate named method or event")
|
||||||
// requires a struct to unpack into for a tuple return...
|
|
||||||
if unpack.isTupleReturn() {
|
|
||||||
return unpack.tupleUnpack(v, output)
|
|
||||||
}
|
|
||||||
return unpack.singleUnpack(v, output)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
var fields []struct {
|
var fields []struct {
|
||||||
Type string
|
Type string
|
||||||
|
|
@ -137,3 +134,14 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MethodById looks up a method by the 4-byte id
|
||||||
|
// returns nil if none found
|
||||||
|
func (abi *ABI) MethodById(sigdata []byte) *Method {
|
||||||
|
for _, method := range abi.Methods {
|
||||||
|
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||||
|
return &method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,15 @@ package abi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ package abi
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Argument holds the name of the argument and the corresponding type.
|
// Argument holds the name of the argument and the corresponding type.
|
||||||
|
|
@ -29,7 +31,10 @@ type Argument struct {
|
||||||
Indexed bool // indexed is only used by events
|
Indexed bool // indexed is only used by events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Argument) UnmarshalJSON(data []byte) error {
|
type Arguments []Argument
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
|
func (argument *Argument) UnmarshalJSON(data []byte) error {
|
||||||
var extarg struct {
|
var extarg struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
|
|
@ -40,12 +45,180 @@ func (a *Argument) UnmarshalJSON(data []byte) error {
|
||||||
return fmt.Errorf("argument json err: %v", err)
|
return fmt.Errorf("argument json err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Type, err = NewType(extarg.Type)
|
argument.Type, err = NewType(extarg.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.Name = extarg.Name
|
argument.Name = extarg.Name
|
||||||
a.Indexed = extarg.Indexed
|
argument.Indexed = extarg.Indexed
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LengthNonIndexed returns the number of arguments when not counting 'indexed' ones. Only events
|
||||||
|
// can ever have 'indexed' arguments, it should always be false on arguments for method input/output
|
||||||
|
func (arguments Arguments) LengthNonIndexed() int {
|
||||||
|
out := 0
|
||||||
|
for _, arg := range arguments {
|
||||||
|
if !arg.Indexed {
|
||||||
|
out++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTuple returns true for non-atomic constructs, like (uint,uint) or uint[]
|
||||||
|
func (arguments Arguments) isTuple() bool {
|
||||||
|
return len(arguments) > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack performs the operation hexdata -> Go format
|
||||||
|
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
||||||
|
if arguments.isTuple() {
|
||||||
|
return arguments.unpackTuple(v, data)
|
||||||
|
}
|
||||||
|
return arguments.unpackAtomic(v, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (arguments Arguments) unpackTuple(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is arguments pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
value = valueOf.Elem()
|
||||||
|
typ = value.Type()
|
||||||
|
kind = value.Kind()
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// `i` counts the nonindexed arguments.
|
||||||
|
// `j` counts the number of complex types.
|
||||||
|
// both `i` and `j` are used to to correctly compute `data` offset.
|
||||||
|
|
||||||
|
i, j := -1, 0
|
||||||
|
for _, arg := range arguments {
|
||||||
|
|
||||||
|
if arg.Indexed {
|
||||||
|
// can't read, continue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
marshalledValue, err := toGoType((i+j)*32, arg.Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if arg.Type.T == ArrayTy {
|
||||||
|
// combined index ('i' + 'j') need to be adjusted only by size of array, thus
|
||||||
|
// we need to decrement 'j' because 'i' was incremented
|
||||||
|
j += arg.Type.Size - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
reflectValue := reflect.ValueOf(marshalledValue)
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Struct:
|
||||||
|
for j := 0; j < typ.NumField(); j++ {
|
||||||
|
field := typ.Field(j)
|
||||||
|
// TODO read tags: `abi:"fieldName"`
|
||||||
|
if field.Name == strings.ToUpper(arg.Name[:1])+arg.Name[1:] {
|
||||||
|
if err := set(value.Field(j), reflectValue, arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if value.Len() < i {
|
||||||
|
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
|
||||||
|
}
|
||||||
|
v := value.Index(i)
|
||||||
|
if err := requireAssignable(v, reflectValue); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := set(v.Elem(), reflectValue, arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi:[2] cannot unmarshal tuple in to %v", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackAtomic unpacks ( hexdata -> go ) a single value
|
||||||
|
func (arguments Arguments) unpackAtomic(v interface{}, output []byte) error {
|
||||||
|
// make sure the passed value is arguments pointer
|
||||||
|
valueOf := reflect.ValueOf(v)
|
||||||
|
if reflect.Ptr != valueOf.Kind() {
|
||||||
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
|
}
|
||||||
|
arg := arguments[0]
|
||||||
|
if arg.Indexed {
|
||||||
|
return fmt.Errorf("abi: attempting to unpack indexed variable into element.")
|
||||||
|
}
|
||||||
|
|
||||||
|
value := valueOf.Elem()
|
||||||
|
|
||||||
|
marshalledValue, err := toGoType(0, arg.Type, output)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return set(value, reflect.ValueOf(marshalledValue), arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack performs the operation Go format -> Hexdata
|
||||||
|
func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
|
||||||
|
// Make sure arguments match up and pack them
|
||||||
|
abiArgs := arguments
|
||||||
|
if len(args) != len(abiArgs) {
|
||||||
|
return nil, fmt.Errorf("argument count mismatch: %d for %d", len(args), len(abiArgs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// variable input is the output appended at the end of packed
|
||||||
|
// output. This is used for strings and bytes types input.
|
||||||
|
var variableInput []byte
|
||||||
|
|
||||||
|
// input offset is the bytes offset for packed output
|
||||||
|
inputOffset := 0
|
||||||
|
for _, abiArg := range abiArgs {
|
||||||
|
if abiArg.Type.T == ArrayTy {
|
||||||
|
inputOffset += (32 * abiArg.Type.Size)
|
||||||
|
} else {
|
||||||
|
inputOffset += 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []byte
|
||||||
|
for i, a := range args {
|
||||||
|
input := abiArgs[i]
|
||||||
|
// pack the input
|
||||||
|
packed, err := input.Type.pack(reflect.ValueOf(a))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for a slice type (string, bytes, slice)
|
||||||
|
if input.Type.requiresLengthPrefix() {
|
||||||
|
// calculate the offset
|
||||||
|
offset := inputOffset + len(variableInput)
|
||||||
|
// set the offset
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
// Append the packed output to the variable input. The variable input
|
||||||
|
// will be appended at the end of the input.
|
||||||
|
variableInput = append(variableInput, packed...)
|
||||||
|
} else {
|
||||||
|
// append the packed value to the input
|
||||||
|
ret = append(ret, packed...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// append the variable input at the end of the packed input
|
||||||
|
ret = append(ret, variableInput...)
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ func (b *SimulatedBackend) Rollback() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *SimulatedBackend) rollback() {
|
func (b *SimulatedBackend) rollback() {
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(int, *core.BlockGen) {})
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
|
||||||
b.pendingBlock = blocks[0]
|
b.pendingBlock = blocks[0]
|
||||||
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
b.pendingState, _ = state.New(b.pendingBlock.Root(), state.NewDatabase(b.database))
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +291,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
|
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
for _, tx := range b.pendingBlock.Transactions() {
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
}
|
}
|
||||||
|
|
@ -306,7 +306,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
||||||
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.database, 1, func(number int, block *core.BlockGen) {
|
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
|
||||||
for _, tx := range b.pendingBlock.Transactions() {
|
for _, tx := range b.pendingBlock.Transactions() {
|
||||||
block.AddTx(tx)
|
block.AddTx(tx)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,8 @@ func packElement(t Type, reflectValue reflect.Value) []byte {
|
||||||
case BoolTy:
|
case BoolTy:
|
||||||
if reflectValue.Bool() {
|
if reflectValue.Bool() {
|
||||||
return math.PaddedBigBytes(common.Big1, 32)
|
return math.PaddedBigBytes(common.Big1, 32)
|
||||||
} else {
|
|
||||||
return math.PaddedBigBytes(common.Big0, 32)
|
|
||||||
}
|
}
|
||||||
|
return math.PaddedBigBytes(common.Big0, 32)
|
||||||
case BytesTy:
|
case BytesTy:
|
||||||
if reflectValue.Kind() == reflect.Array {
|
if reflectValue.Kind() == reflect.Array {
|
||||||
reflectValue = mustArrayToByteSlice(reflectValue)
|
reflectValue = mustArrayToByteSlice(reflectValue)
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,28 @@ func set(dst, src reflect.Value, output Argument) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// requireAssignable assures that `dest` is a pointer and it's not an interface.
|
||||||
|
func requireAssignable(dst, src reflect.Value) error {
|
||||||
|
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal %v into %v", src.Type(), dst.Type())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// requireUnpackKind verifies preconditions for unpacking `args` into `kind`
|
||||||
|
func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
|
||||||
|
args Arguments) error {
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case reflect.Struct:
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
if minLen := args.LengthNonIndexed(); v.Len() < minLen {
|
||||||
|
return fmt.Errorf("abi: insufficient number of elements in the list/array for unpack, want %d, got %d",
|
||||||
|
minLen, v.Len())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("abi: cannot unmarshal tuple into %v", t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Type enumerator
|
||||||
const (
|
const (
|
||||||
IntTy byte = iota
|
IntTy byte = iota
|
||||||
UintTy
|
UintTy
|
||||||
|
|
@ -100,69 +101,66 @@ func NewType(t string) (typ Type, err error) {
|
||||||
return Type{}, fmt.Errorf("invalid formatting of array type")
|
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||||
}
|
}
|
||||||
return typ, err
|
return typ, err
|
||||||
} else {
|
}
|
||||||
// parse the type and size of the abi-type.
|
// parse the type and size of the abi-type.
|
||||||
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
|
||||||
// varSize is the size of the variable
|
// varSize is the size of the variable
|
||||||
var varSize int
|
var varSize int
|
||||||
if len(parsedType[3]) > 0 {
|
if len(parsedType[3]) > 0 {
|
||||||
var err error
|
var err error
|
||||||
varSize, err = strconv.Atoi(parsedType[2])
|
varSize, err = strconv.Atoi(parsedType[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
|
||||||
// this should fail because it means that there's something wrong with
|
|
||||||
// the abi type (the compiler should always format it to the size...always)
|
|
||||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// varType is the parsed abi type
|
} else {
|
||||||
varType := parsedType[1]
|
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||||
|
// this should fail because it means that there's something wrong with
|
||||||
switch varType {
|
// the abi type (the compiler should always format it to the size...always)
|
||||||
case "int":
|
|
||||||
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
|
||||||
typ.Size = varSize
|
|
||||||
typ.T = IntTy
|
|
||||||
case "uint":
|
|
||||||
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
|
||||||
typ.Size = varSize
|
|
||||||
typ.T = UintTy
|
|
||||||
case "bool":
|
|
||||||
typ.Kind = reflect.Bool
|
|
||||||
typ.T = BoolTy
|
|
||||||
typ.Type = reflect.TypeOf(bool(false))
|
|
||||||
case "address":
|
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Type = address_t
|
|
||||||
typ.Size = 20
|
|
||||||
typ.T = AddressTy
|
|
||||||
case "string":
|
|
||||||
typ.Kind = reflect.String
|
|
||||||
typ.Type = reflect.TypeOf("")
|
|
||||||
typ.T = StringTy
|
|
||||||
case "bytes":
|
|
||||||
if varSize == 0 {
|
|
||||||
typ.T = BytesTy
|
|
||||||
typ.Kind = reflect.Slice
|
|
||||||
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
|
||||||
} else {
|
|
||||||
typ.T = FixedBytesTy
|
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.Size = varSize
|
|
||||||
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
|
||||||
}
|
|
||||||
case "function":
|
|
||||||
typ.Kind = reflect.Array
|
|
||||||
typ.T = FunctionTy
|
|
||||||
typ.Size = 24
|
|
||||||
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
|
||||||
default:
|
|
||||||
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// varType is the parsed abi type
|
||||||
|
switch varType := parsedType[1]; varType {
|
||||||
|
case "int":
|
||||||
|
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.T = IntTy
|
||||||
|
case "uint":
|
||||||
|
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.T = UintTy
|
||||||
|
case "bool":
|
||||||
|
typ.Kind = reflect.Bool
|
||||||
|
typ.T = BoolTy
|
||||||
|
typ.Type = reflect.TypeOf(bool(false))
|
||||||
|
case "address":
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Type = address_t
|
||||||
|
typ.Size = 20
|
||||||
|
typ.T = AddressTy
|
||||||
|
case "string":
|
||||||
|
typ.Kind = reflect.String
|
||||||
|
typ.Type = reflect.TypeOf("")
|
||||||
|
typ.T = StringTy
|
||||||
|
case "bytes":
|
||||||
|
if varSize == 0 {
|
||||||
|
typ.T = BytesTy
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||||
|
} else {
|
||||||
|
typ.T = FixedBytesTy
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||||
|
}
|
||||||
|
case "function":
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.T = FunctionTy
|
||||||
|
typ.Size = 24
|
||||||
|
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
||||||
|
default:
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,15 +25,6 @@ import (
|
||||||
"github.com/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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ func decryptPreSaleKey(fileContent []byte, password string) (key *Key, err error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid hex in encSeed")
|
return nil, errors.New("invalid hex in encSeed")
|
||||||
}
|
}
|
||||||
|
if len(encSeedBytes) < 16 {
|
||||||
|
return nil, errors.New("invalid encSeed, too short")
|
||||||
|
}
|
||||||
iv := encSeedBytes[:16]
|
iv := encSeedBytes[:16]
|
||||||
cipherText := encSeedBytes[16:]
|
cipherText := encSeedBytes[16:]
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.9.2.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.9.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.9.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
|
||||||
41
cmd/ethkey/README.md
Normal file
41
cmd/ethkey/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
ethkey
|
||||||
|
======
|
||||||
|
|
||||||
|
ethkey is a simple command-line tool for working with Ethereum keyfiles.
|
||||||
|
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
### `ethkey generate`
|
||||||
|
|
||||||
|
Generate a new keyfile.
|
||||||
|
If you want to use an existing private key to use in the keyfile, it can be
|
||||||
|
specified by setting `--privatekey` with the location of the file containing the
|
||||||
|
private key.
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey inspect <keyfile>`
|
||||||
|
|
||||||
|
Print various information about the keyfile.
|
||||||
|
Private key information can be printed by using the `--private` flag;
|
||||||
|
make sure to use this feature with great caution!
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey sign <keyfile> <message/file>`
|
||||||
|
|
||||||
|
Sign the message with a keyfile.
|
||||||
|
It is possible to refer to a file containing the message.
|
||||||
|
|
||||||
|
|
||||||
|
### `ethkey verify <address> <signature> <message/file>`
|
||||||
|
|
||||||
|
Verify the signature of the message.
|
||||||
|
It is possible to refer to a file containing the message.
|
||||||
|
|
||||||
|
|
||||||
|
## Passphrases
|
||||||
|
|
||||||
|
For every command that uses a keyfile, you will be prompted to provide the
|
||||||
|
passphrase for decrypting the keyfile. To avoid this message, it is possible
|
||||||
|
to pass the passphrase by using the `--passphrase` flag pointing to a file that
|
||||||
|
contains the passphrase.
|
||||||
117
cmd/ethkey/generate.go
Normal file
117
cmd/ethkey/generate.go
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/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
74
cmd/ethkey/inspect.go
Normal 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
70
cmd/ethkey/main.go
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/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
148
cmd/ethkey/message.go
Normal 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
83
cmd/ethkey/utils.go
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/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))
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}}", \
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,13 @@ func getFdLimit() (int, error) {
|
||||||
}
|
}
|
||||||
return int(limit.Cur), nil
|
return int(limit.Cur), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func getFdMaxLimit() (int, error) {
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(limit.Max), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,22 @@
|
||||||
|
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
|
// TestFileDescriptorLimits simply tests whether the file descriptor allowance
|
||||||
// per this process can be retrieved.
|
// per this process can be retrieved.
|
||||||
func TestFileDescriptorLimits(t *testing.T) {
|
func TestFileDescriptorLimits(t *testing.T) {
|
||||||
target := 4096
|
target := 4096
|
||||||
|
hardlimit, err := getFdMaxLimit()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hardlimit < target {
|
||||||
|
t.Skip(fmt.Sprintf("system limit is less than desired test target: %d < %d", hardlimit, target))
|
||||||
|
}
|
||||||
|
|
||||||
if limit, err := getFdLimit(); err != nil || limit <= 0 {
|
if limit, err := getFdLimit(); err != nil || limit <= 0 {
|
||||||
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
|
t.Fatalf("failed to retrieve file descriptor limit (%d): %v", limit, err)
|
||||||
|
|
|
||||||
|
|
@ -48,3 +48,13 @@ func getFdLimit() (int, error) {
|
||||||
}
|
}
|
||||||
return int(limit.Cur), nil
|
return int(limit.Cur), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func getFdMaxLimit() (int, error) {
|
||||||
|
var limit syscall.Rlimit
|
||||||
|
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(limit.Max), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,9 @@ func getFdLimit() (int, error) {
|
||||||
// Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
|
// Please see raiseFdLimit for the reason why we use hard coded 16K as the limit
|
||||||
return 16384, nil
|
return 16384, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFdMaxLimit retrieves the maximum number of file descriptors this process is
|
||||||
|
// allowed to request for itself.
|
||||||
|
func getFdMaxLimit() (int, error) {
|
||||||
|
return getFdLimit()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ func TestCalcDifficulty(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)}
|
config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
|
number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1))
|
||||||
diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{
|
diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,9 @@ func New(config Config) (*Console, error) {
|
||||||
printer: config.Printer,
|
printer: config.Printer,
|
||||||
histPath: filepath.Join(config.DataDir, HistoryFile),
|
histPath: filepath.Join(config.DataDir, HistoryFile),
|
||||||
}
|
}
|
||||||
|
if err := os.MkdirAll(config.DataDir, 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := console.init(config.Preload); err != nil {
|
if err := console.init(config.Preload); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
|
||||||
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
|
Alloc: GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}},
|
||||||
}
|
}
|
||||||
genesis := gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, b.N, gen)
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, b.N, gen)
|
||||||
|
|
||||||
// Time the insertion of the new chain.
|
// Time the insertion of the new chain.
|
||||||
// State and blocks are stored in the same DB.
|
// State and blocks are stored in the same DB.
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func TestHeaderVerification(t *testing.T) {
|
||||||
testdb, _ = ethdb.NewMemDatabase()
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
gspec = &Genesis{Config: params.TestChainConfig}
|
gspec = &Genesis{Config: params.TestChainConfig}
|
||||||
genesis = gspec.MustCommit(testdb)
|
genesis = gspec.MustCommit(testdb)
|
||||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil)
|
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||||
)
|
)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
for i, block := range blocks {
|
for i, block := range blocks {
|
||||||
|
|
@ -87,7 +87,7 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
|
||||||
testdb, _ = ethdb.NewMemDatabase()
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
gspec = &Genesis{Config: params.TestChainConfig}
|
gspec = &Genesis{Config: params.TestChainConfig}
|
||||||
genesis = gspec.MustCommit(testdb)
|
genesis = gspec.MustCommit(testdb)
|
||||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 8, nil)
|
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 8, nil)
|
||||||
)
|
)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
seals := make([]bool, len(blocks))
|
seals := make([]bool, len(blocks))
|
||||||
|
|
@ -159,7 +159,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) {
|
||||||
testdb, _ = ethdb.NewMemDatabase()
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
gspec = &Genesis{Config: params.TestChainConfig}
|
gspec = &Genesis{Config: params.TestChainConfig}
|
||||||
genesis = gspec.MustCommit(testdb)
|
genesis = gspec.MustCommit(testdb)
|
||||||
blocks, _ = GenerateChain(params.TestChainConfig, genesis, testdb, 1024, nil)
|
blocks, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), testdb, 1024, nil)
|
||||||
)
|
)
|
||||||
headers := make([]*types.Header, len(blocks))
|
headers := make([]*types.Header, len(blocks))
|
||||||
seals := make([]bool, len(blocks))
|
seals := make([]bool, len(blocks))
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ func newTestBlockChain(fake bool) *BlockChain {
|
||||||
// Test fork of length N starting from block i
|
// Test fork of length N starting from block i
|
||||||
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
|
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
|
||||||
// Copy old chain up to #i into a new db
|
// Copy old chain up to #i into a new db
|
||||||
db, blockchain2, err := newCanonical(i, full)
|
db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not make new canonical in testFork", err)
|
t.Fatal("could not make new canonical in testFork", err)
|
||||||
}
|
}
|
||||||
|
|
@ -81,12 +81,12 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara
|
||||||
headerChainB []*types.Header
|
headerChainB []*types.Header
|
||||||
)
|
)
|
||||||
if full {
|
if full {
|
||||||
blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, db, forkSeed)
|
blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed)
|
||||||
if _, err := blockchain2.InsertChain(blockChainB); err != nil {
|
if _, err := blockchain2.InsertChain(blockChainB); err != nil {
|
||||||
t.Fatalf("failed to insert forking chain: %v", err)
|
t.Fatalf("failed to insert forking chain: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, db, forkSeed)
|
headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed)
|
||||||
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
|
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
|
||||||
t.Fatalf("failed to insert forking chain: %v", err)
|
t.Fatalf("failed to insert forking chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +186,7 @@ func TestLastBlock(t *testing.T) {
|
||||||
bchain := newTestBlockChain(false)
|
bchain := newTestBlockChain(false)
|
||||||
defer bchain.Stop()
|
defer bchain.Stop()
|
||||||
|
|
||||||
block := makeBlockChain(bchain.CurrentBlock(), 1, bchain.chainDb, 0)[0]
|
block := makeBlockChain(bchain.CurrentBlock(), 1, ethash.NewFaker(), bchain.chainDb, 0)[0]
|
||||||
bchain.insert(block)
|
bchain.insert(block)
|
||||||
if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
|
if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
|
||||||
t.Errorf("Write/Get HeadBlockHash failed")
|
t.Errorf("Write/Get HeadBlockHash failed")
|
||||||
|
|
@ -202,7 +202,7 @@ func testExtendCanonical(t *testing.T, full bool) {
|
||||||
length := 5
|
length := 5
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +230,7 @@ func testShorterFork(t *testing.T, full bool) {
|
||||||
length := 10
|
length := 10
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +260,7 @@ func testLongerFork(t *testing.T, full bool) {
|
||||||
length := 10
|
length := 10
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -290,7 +290,7 @@ func testEqualFork(t *testing.T, full bool) {
|
||||||
length := 10
|
length := 10
|
||||||
|
|
||||||
// Make first chain starting from genesis
|
// Make first chain starting from genesis
|
||||||
_, processor, err := newCanonical(length, full)
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +317,7 @@ func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) }
|
||||||
|
|
||||||
func testBrokenChain(t *testing.T, full bool) {
|
func testBrokenChain(t *testing.T, full bool) {
|
||||||
// Make chain starting from genesis
|
// Make chain starting from genesis
|
||||||
db, blockchain, err := newCanonical(10, full)
|
db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to make new canonical chain: %v", err)
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -325,12 +325,12 @@ func testBrokenChain(t *testing.T, full bool) {
|
||||||
|
|
||||||
// Create a forked chain, and try to insert with a missing link
|
// Create a forked chain, and try to insert with a missing link
|
||||||
if full {
|
if full {
|
||||||
chain := makeBlockChain(blockchain.CurrentBlock(), 5, db, forkSeed)[1:]
|
chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed)[1:]
|
||||||
if err := testBlockChainImport(chain, blockchain); err == nil {
|
if err := testBlockChainImport(chain, blockchain); err == nil {
|
||||||
t.Errorf("broken block chain not reported")
|
t.Errorf("broken block chain not reported")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chain := makeHeaderChain(blockchain.CurrentHeader(), 5, db, forkSeed)[1:]
|
chain := makeHeaderChain(blockchain.CurrentHeader(), 5, ethash.NewFaker(), db, forkSeed)[1:]
|
||||||
if err := testHeaderChainImport(chain, blockchain); err == nil {
|
if err := testHeaderChainImport(chain, blockchain); err == nil {
|
||||||
t.Errorf("broken header chain not reported")
|
t.Errorf("broken header chain not reported")
|
||||||
}
|
}
|
||||||
|
|
@ -523,7 +523,7 @@ func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) }
|
||||||
func testInsertNonceError(t *testing.T, full bool) {
|
func testInsertNonceError(t *testing.T, full bool) {
|
||||||
for i := 1; i < 25 && !t.Failed(); i++ {
|
for i := 1; i < 25 && !t.Failed(); i++ {
|
||||||
// Create a pristine chain and database
|
// Create a pristine chain and database
|
||||||
db, blockchain, err := newCanonical(0, full)
|
db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create pristine chain: %v", err)
|
t.Fatalf("failed to create pristine chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -536,7 +536,7 @@ func testInsertNonceError(t *testing.T, full bool) {
|
||||||
failNum uint64
|
failNum uint64
|
||||||
)
|
)
|
||||||
if full {
|
if full {
|
||||||
blocks := makeBlockChain(blockchain.CurrentBlock(), i, db, 0)
|
blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0)
|
||||||
|
|
||||||
failAt = rand.Int() % len(blocks)
|
failAt = rand.Int() % len(blocks)
|
||||||
failNum = blocks[failAt].NumberU64()
|
failNum = blocks[failAt].NumberU64()
|
||||||
|
|
@ -544,7 +544,7 @@ func testInsertNonceError(t *testing.T, full bool) {
|
||||||
blockchain.engine = ethash.NewFakeFailer(failNum)
|
blockchain.engine = ethash.NewFakeFailer(failNum)
|
||||||
failRes, err = blockchain.InsertChain(blocks)
|
failRes, err = blockchain.InsertChain(blocks)
|
||||||
} else {
|
} else {
|
||||||
headers := makeHeaderChain(blockchain.CurrentHeader(), i, db, 0)
|
headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0)
|
||||||
|
|
||||||
failAt = rand.Int() % len(headers)
|
failAt = rand.Int() % len(headers)
|
||||||
failNum = headers[failAt].Number.Uint64()
|
failNum = headers[failAt].Number.Uint64()
|
||||||
|
|
@ -588,7 +588,7 @@ func TestFastVsFullChains(t *testing.T) {
|
||||||
genesis = gspec.MustCommit(gendb)
|
genesis = gspec.MustCommit(gendb)
|
||||||
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
||||||
)
|
)
|
||||||
blocks, receipts := GenerateChain(gspec.Config, genesis, gendb, 1024, func(i int, block *BlockGen) {
|
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) {
|
||||||
block.SetCoinbase(common.Address{0x00})
|
block.SetCoinbase(common.Address{0x00})
|
||||||
|
|
||||||
// If the block number is multiple of 3, send a few bonus transactions to the miner
|
// If the block number is multiple of 3, send a few bonus transactions to the miner
|
||||||
|
|
@ -673,7 +673,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
||||||
genesis = gspec.MustCommit(gendb)
|
genesis = gspec.MustCommit(gendb)
|
||||||
)
|
)
|
||||||
height := uint64(1024)
|
height := uint64(1024)
|
||||||
blocks, receipts := GenerateChain(gspec.Config, genesis, gendb, int(height), nil)
|
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), nil)
|
||||||
|
|
||||||
// Configure a subchain to roll back
|
// Configure a subchain to roll back
|
||||||
remove := []common.Hash{}
|
remove := []common.Hash{}
|
||||||
|
|
@ -781,7 +781,7 @@ func TestChainTxReorgs(t *testing.T) {
|
||||||
// - futureAdd: transaction added after the reorg has already finished
|
// - futureAdd: transaction added after the reorg has already finished
|
||||||
var pastAdd, freshAdd, futureAdd *types.Transaction
|
var pastAdd, freshAdd, futureAdd *types.Transaction
|
||||||
|
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, gen *BlockGen) {
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
|
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
|
||||||
|
|
@ -806,7 +806,7 @@ func TestChainTxReorgs(t *testing.T) {
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
// overwrite the old chain
|
// overwrite the old chain
|
||||||
chain, _ = GenerateChain(gspec.Config, genesis, db, 5, func(i int, gen *BlockGen) {
|
chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
||||||
|
|
@ -875,7 +875,7 @@ func TestLogReorgs(t *testing.T) {
|
||||||
|
|
||||||
rmLogsCh := make(chan RemovedLogsEvent)
|
rmLogsCh := make(chan RemovedLogsEvent)
|
||||||
blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
|
blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
|
||||||
chain, _ := GenerateChain(params.TestChainConfig, genesis, db, 2, func(i int, gen *BlockGen) {
|
chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
|
||||||
if i == 1 {
|
if i == 1 {
|
||||||
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
|
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -888,7 +888,7 @@ func TestLogReorgs(t *testing.T) {
|
||||||
t.Fatalf("failed to insert chain: %v", err)
|
t.Fatalf("failed to insert chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chain, _ = GenerateChain(params.TestChainConfig, genesis, db, 3, func(i int, gen *BlockGen) {})
|
chain, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
|
||||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
t.Fatalf("failed to insert forked chain: %v", err)
|
t.Fatalf("failed to insert forked chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -920,12 +920,12 @@ func TestReorgSideEvent(t *testing.T) {
|
||||||
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, gen *BlockGen) {})
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
|
||||||
if _, err := blockchain.InsertChain(chain); err != nil {
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
||||||
t.Fatalf("failed to insert chain: %v", err)
|
t.Fatalf("failed to insert chain: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
replacementBlocks, _ := GenerateChain(gspec.Config, genesis, db, 4, func(i int, gen *BlockGen) {
|
replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) {
|
||||||
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1)
|
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1)
|
||||||
if i == 2 {
|
if i == 2 {
|
||||||
gen.OffsetTime(-9)
|
gen.OffsetTime(-9)
|
||||||
|
|
@ -992,7 +992,7 @@ func TestCanonicalBlockRetrieval(t *testing.T) {
|
||||||
bc := newTestBlockChain(true)
|
bc := newTestBlockChain(true)
|
||||||
defer bc.Stop()
|
defer bc.Stop()
|
||||||
|
|
||||||
chain, _ := GenerateChain(bc.config, bc.genesisBlock, bc.chainDb, 10, func(i int, gen *BlockGen) {})
|
chain, _ := GenerateChain(bc.config, bc.genesisBlock, ethash.NewFaker(), bc.chainDb, 10, func(i int, gen *BlockGen) {})
|
||||||
|
|
||||||
var pend sync.WaitGroup
|
var pend sync.WaitGroup
|
||||||
pend.Add(len(chain))
|
pend.Add(len(chain))
|
||||||
|
|
@ -1046,7 +1046,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||||
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
blocks, _ := GenerateChain(gspec.Config, genesis, db, 4, func(i int, block *BlockGen) {
|
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
||||||
var (
|
var (
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
err error
|
err error
|
||||||
|
|
@ -1109,7 +1109,7 @@ func TestEIP155Transition(t *testing.T) {
|
||||||
|
|
||||||
// generate an invalid chain id transaction
|
// generate an invalid chain id transaction
|
||||||
config := ¶ms.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
config := ¶ms.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
||||||
blocks, _ = GenerateChain(config, blocks[len(blocks)-1], db, 4, func(i int, block *BlockGen) {
|
blocks, _ = GenerateChain(config, blocks[len(blocks)-1], ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
||||||
var (
|
var (
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
err error
|
err error
|
||||||
|
|
@ -1154,7 +1154,7 @@ func TestEIP161AccountRemoval(t *testing.T) {
|
||||||
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
||||||
defer blockchain.Stop()
|
defer blockchain.Stop()
|
||||||
|
|
||||||
blocks, _ := GenerateChain(gspec.Config, genesis, db, 3, func(i int, block *BlockGen) {
|
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, block *BlockGen) {
|
||||||
var (
|
var (
|
||||||
tx *types.Transaction
|
tx *types.Transaction
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/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
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func ExampleGenerateChain() {
|
||||||
// each block and adds different features to gen based on the
|
// each block and adds different features to gen based on the
|
||||||
// block index.
|
// block index.
|
||||||
signer := types.HomesteadSigner{}
|
signer := types.HomesteadSigner{}
|
||||||
chain, _ := GenerateChain(gspec.Config, genesis, db, 5, func(i int, gen *BlockGen) {
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
|
||||||
switch i {
|
switch i {
|
||||||
case 0:
|
case 0:
|
||||||
// In block 1, addr1 sends addr2 some ether.
|
// In block 1, addr1 sends addr2 some ether.
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
db, _ := ethdb.NewMemDatabase()
|
db, _ := ethdb.NewMemDatabase()
|
||||||
gspec := new(Genesis)
|
gspec := new(Genesis)
|
||||||
genesis := gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
prefix, _ := GenerateChain(params.TestChainConfig, genesis, db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
prefix, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, int(forkBlock.Int64()-1), func(i int, gen *BlockGen) {})
|
||||||
|
|
||||||
// Create the concurrent, conflicting two nodes
|
// Create the concurrent, conflicting two nodes
|
||||||
proDb, _ := ethdb.NewMemDatabase()
|
proDb, _ := ethdb.NewMemDatabase()
|
||||||
|
|
@ -79,12 +79,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := conBc.InsertChain(blocks); err == nil {
|
if _, err := conBc.InsertChain(blocks); err == nil {
|
||||||
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
|
t.Fatalf("contra-fork chain accepted pro-fork block: %v", blocks[0])
|
||||||
}
|
}
|
||||||
// Create a proper no-fork block for the contra-forker
|
// Create a proper no-fork block for the contra-forker
|
||||||
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&conConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := conBc.InsertChain(blocks); err != nil {
|
if _, err := conBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
|
t.Fatalf("contra-fork chain didn't accepted no-fork block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -101,12 +101,12 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := proBc.InsertChain(blocks); err == nil {
|
if _, err := proBc.InsertChain(blocks); err == nil {
|
||||||
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
|
t.Fatalf("pro-fork chain accepted contra-fork block: %v", blocks[0])
|
||||||
}
|
}
|
||||||
// Create a proper pro-fork block for the pro-forker
|
// Create a proper pro-fork block for the pro-forker
|
||||||
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&proConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := proBc.InsertChain(blocks); err != nil {
|
if _, err := proBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
|
t.Fatalf("pro-fork chain didn't accepted pro-fork block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import contra-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&proConf, conBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := conBc.InsertChain(blocks); err != nil {
|
if _, err := conBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
|
t.Fatalf("contra-fork chain didn't accept pro-fork block post-fork: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +141,7 @@ func TestDAOForkRangeExtradata(t *testing.T) {
|
||||||
if _, err := bc.InsertChain(blocks); err != nil {
|
if _, err := bc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
t.Fatalf("failed to import pro-fork chain for expansion: %v", err)
|
||||||
}
|
}
|
||||||
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), db, 1, func(i int, gen *BlockGen) {})
|
blocks, _ = GenerateChain(&conConf, proBc.CurrentBlock(), ethash.NewFaker(), db, 1, func(i int, gen *BlockGen) {})
|
||||||
if _, err := proBc.InsertChain(blocks); err != nil {
|
if _, err := proBc.InsertChain(blocks); err != nil {
|
||||||
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
|
t.Fatalf("pro-fork chain didn't accept contra-fork block post-fork: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ var (
|
||||||
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
underpricedTxCounter = metrics.NewCounter("txpool/underpriced")
|
||||||
)
|
)
|
||||||
|
|
||||||
// TxStatus is the current status of a transaction as seen py the pool.
|
// TxStatus is the current status of a transaction as seen by the pool.
|
||||||
type TxStatus uint
|
type TxStatus uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -199,7 +199,7 @@ type TxPool struct {
|
||||||
pendingState *state.ManagedState // Pending state tracking virtual nonces
|
pendingState *state.ManagedState // Pending state tracking virtual nonces
|
||||||
currentMaxGas *big.Int // Current gas limit for transaction caps
|
currentMaxGas *big.Int // Current gas limit for transaction caps
|
||||||
|
|
||||||
locals *accountSet // Set of local transaction to exepmt from evicion rules
|
locals *accountSet // Set of local transaction to exempt from eviction rules
|
||||||
journal *txJournal // Journal of local transaction to back up to disk
|
journal *txJournal // Journal of local transaction to back up to disk
|
||||||
|
|
||||||
pending map[common.Address]*txList // All currently processable transactions
|
pending map[common.Address]*txList // All currently processable transactions
|
||||||
|
|
@ -214,7 +214,7 @@ type TxPool struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
// NewTxPool creates a new transaction pool to gather, sort and filter inbound
|
||||||
// trnsactions from the network.
|
// transactions from the network.
|
||||||
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain blockChain) *TxPool {
|
||||||
// Sanitize the input to ensure no vulnerable gas prices are set
|
// Sanitize the input to ensure no vulnerable gas prices are set
|
||||||
config = (&config).sanitize()
|
config = (&config).sanitize()
|
||||||
|
|
@ -360,7 +360,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||||
newNum := newHead.Number.Uint64()
|
newNum := newHead.Number.Uint64()
|
||||||
|
|
||||||
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
|
||||||
log.Warn("Skipping deep transaction reorg", "depth", depth)
|
log.Debug("Skipping deep transaction reorg", "depth", depth)
|
||||||
} else {
|
} else {
|
||||||
// Reorg seems shallow enough to pull in all transactions into memory
|
// Reorg seems shallow enough to pull in all transactions into memory
|
||||||
var discarded, included types.Transactions
|
var discarded, included types.Transactions
|
||||||
|
|
@ -838,7 +838,7 @@ func (pool *TxPool) Status(hashes []common.Hash) []TxStatus {
|
||||||
for i, hash := range hashes {
|
for i, hash := range hashes {
|
||||||
if tx := pool.all[hash]; tx != nil {
|
if tx := pool.all[hash]; tx != nil {
|
||||||
from, _ := types.Sender(pool.signer, tx) // already validated
|
from, _ := types.Sender(pool.signer, tx) // already validated
|
||||||
if pool.pending[from].txs.items[tx.Nonce()] != nil {
|
if pool.pending[from] != nil && pool.pending[from].txs.items[tx.Nonce()] != nil {
|
||||||
status[i] = TxStatusPending
|
status[i] = TxStatusPending
|
||||||
} else {
|
} else {
|
||||||
status[i] = TxStatusQueued
|
status[i] = TxStatusQueued
|
||||||
|
|
|
||||||
|
|
@ -1563,6 +1563,63 @@ func testTransactionJournaling(t *testing.T, nolocals bool) {
|
||||||
pool.Stop()
|
pool.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTransactionStatusCheck tests that the pool can correctly retrieve the
|
||||||
|
// pending status of individual transactions.
|
||||||
|
func TestTransactionStatusCheck(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create the pool to test the status retrievals with
|
||||||
|
db, _ := ethdb.NewMemDatabase()
|
||||||
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||||
|
blockchain := &testBlockChain{statedb, big.NewInt(1000000), new(event.Feed)}
|
||||||
|
|
||||||
|
pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain)
|
||||||
|
defer pool.Stop()
|
||||||
|
|
||||||
|
// Create the test accounts to check various transaction statuses with
|
||||||
|
keys := make([]*ecdsa.PrivateKey, 3)
|
||||||
|
for i := 0; i < len(keys); i++ {
|
||||||
|
keys[i], _ = crypto.GenerateKey()
|
||||||
|
pool.currentState.AddBalance(crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(1000000))
|
||||||
|
}
|
||||||
|
// Generate and queue a batch of transactions, both pending and queued
|
||||||
|
txs := types.Transactions{}
|
||||||
|
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[0])) // Pending only
|
||||||
|
txs = append(txs, pricedTransaction(0, big.NewInt(100000), big.NewInt(1), keys[1])) // Pending and queued
|
||||||
|
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[1]))
|
||||||
|
txs = append(txs, pricedTransaction(2, big.NewInt(100000), big.NewInt(1), keys[2])) // Queued only
|
||||||
|
|
||||||
|
// Import the transaction and ensure they are correctly added
|
||||||
|
pool.AddRemotes(txs)
|
||||||
|
|
||||||
|
pending, queued := pool.Stats()
|
||||||
|
if pending != 2 {
|
||||||
|
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2)
|
||||||
|
}
|
||||||
|
if queued != 2 {
|
||||||
|
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
|
||||||
|
}
|
||||||
|
if err := validateTxPoolInternals(pool); err != nil {
|
||||||
|
t.Fatalf("pool internal state corrupted: %v", err)
|
||||||
|
}
|
||||||
|
// Retrieve the status of each transaction and validate them
|
||||||
|
hashes := make([]common.Hash, len(txs))
|
||||||
|
for i, tx := range txs {
|
||||||
|
hashes[i] = tx.Hash()
|
||||||
|
}
|
||||||
|
hashes = append(hashes, common.Hash{})
|
||||||
|
|
||||||
|
statuses := pool.Status(hashes)
|
||||||
|
expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown}
|
||||||
|
|
||||||
|
for i := 0; i < len(statuses); i++ {
|
||||||
|
if statuses[i] != expect[i] {
|
||||||
|
t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Benchmarks the speed of validating the contents of the pending queue of the
|
// Benchmarks the speed of validating the contents of the pending queue of the
|
||||||
// transaction pool.
|
// transaction pool.
|
||||||
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,7 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON encodes the web3 RPC transaction format.
|
||||||
func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
func (tx *Transaction) MarshalJSON() ([]byte, error) {
|
||||||
hash := tx.Hash()
|
hash := tx.Hash()
|
||||||
data := tx.data
|
data := tx.data
|
||||||
|
|
@ -168,8 +169,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
}
|
}
|
||||||
var V byte
|
var V byte
|
||||||
if isProtectedV(dec.V) {
|
if isProtectedV(dec.V) {
|
||||||
chainId := deriveChainId(dec.V).Uint64()
|
chainID := deriveChainId(dec.V).Uint64()
|
||||||
V = byte(dec.V.Uint64() - 35 - 2*chainId)
|
V = byte(dec.V.Uint64() - 35 - 2*chainID)
|
||||||
} else {
|
} else {
|
||||||
V = byte(dec.V.Uint64() - 27)
|
V = byte(dec.V.Uint64() - 27)
|
||||||
}
|
}
|
||||||
|
|
@ -192,10 +193,9 @@ func (tx *Transaction) CheckNonce() bool { return true }
|
||||||
func (tx *Transaction) To() *common.Address {
|
func (tx *Transaction) To() *common.Address {
|
||||||
if tx.data.Recipient == nil {
|
if tx.data.Recipient == nil {
|
||||||
return nil
|
return nil
|
||||||
} else {
|
|
||||||
to := *tx.data.Recipient
|
|
||||||
return &to
|
|
||||||
}
|
}
|
||||||
|
to := *tx.data.Recipient
|
||||||
|
return &to
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash hashes the RLP encoding of tx.
|
// Hash hashes the RLP encoding of tx.
|
||||||
|
|
@ -315,22 +315,22 @@ func (tx *Transaction) String() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction slice type for basic sorting.
|
// Transactions is a Transaction slice type for basic sorting.
|
||||||
type Transactions []*Transaction
|
type Transactions []*Transaction
|
||||||
|
|
||||||
// Len returns the length of s
|
// Len returns the length of s.
|
||||||
func (s Transactions) Len() int { return len(s) }
|
func (s Transactions) Len() int { return len(s) }
|
||||||
|
|
||||||
// Swap swaps the i'th and the j'th element in s
|
// Swap swaps the i'th and the j'th element in s.
|
||||||
func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s Transactions) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
// GetRlp implements Rlpable and returns the i'th element of s in rlp
|
// GetRlp implements Rlpable and returns the i'th element of s in rlp.
|
||||||
func (s Transactions) GetRlp(i int) []byte {
|
func (s Transactions) GetRlp(i int) []byte {
|
||||||
enc, _ := rlp.EncodeToBytes(s[i])
|
enc, _ := rlp.EncodeToBytes(s[i])
|
||||||
return enc
|
return enc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new set t which is the difference between a to b
|
// TxDifference returns a new set t which is the difference between a to b.
|
||||||
func TxDifference(a, b Transactions) (keep Transactions) {
|
func TxDifference(a, b Transactions) (keep Transactions) {
|
||||||
keep = make(Transactions, 0, len(a))
|
keep = make(Transactions, 0, len(a))
|
||||||
|
|
||||||
|
|
@ -378,7 +378,7 @@ func (s *TxByPrice) Pop() interface{} {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransactionsByPriceAndNonce represents a set of transactions that can return
|
// TransactionsByPriceAndNonce represents a set of transactions that can return
|
||||||
// transactions in a profit-maximising sorted order, while supporting removing
|
// transactions in a profit-maximizing sorted order, while supporting removing
|
||||||
// entire batches of transactions for non-executable accounts.
|
// entire batches of transactions for non-executable accounts.
|
||||||
type TransactionsByPriceAndNonce struct {
|
type TransactionsByPriceAndNonce struct {
|
||||||
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions
|
txs map[common.Address]Transactions // Per account nonce-sorted list of transactions
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package vm
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,9 @@ func (in *Interpreter) enforceRestrictions(op OpCode, operation operation, stack
|
||||||
// the return byte-slice and an error if one occurred.
|
// the return byte-slice and an error if one occurred.
|
||||||
//
|
//
|
||||||
// It's important to note that any errors returned by the interpreter should be
|
// It's important to note that any errors returned by the interpreter should be
|
||||||
// considered a revert-and-consume-all-gas operation. No error specific checks
|
// considered a revert-and-consume-all-gas operation except for
|
||||||
// should be handled to reduce complexity and errors further down the in.
|
// errExecutionReverted which means revert-and-keep-gas-left.
|
||||||
func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret []byte, err error) {
|
func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) {
|
||||||
// Increment the call depth which is restricted to 1024
|
// Increment the call depth which is restricted to 1024
|
||||||
in.evm.depth++
|
in.evm.depth++
|
||||||
defer func() { in.evm.depth-- }()
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
@ -144,12 +144,17 @@ func (in *Interpreter) Run(snapshot int, contract *Contract, input []byte) (ret
|
||||||
)
|
)
|
||||||
contract.Input = input
|
contract.Input = input
|
||||||
|
|
||||||
defer func() {
|
if in.cfg.Debug {
|
||||||
if err != nil && !logged && in.cfg.Debug {
|
defer func() {
|
||||||
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
if err != nil {
|
||||||
}
|
if !logged {
|
||||||
}()
|
in.cfg.Tracer.CaptureState(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
||||||
|
} else {
|
||||||
|
in.cfg.Tracer.CaptureFault(in.evm, pcCopy, op, gasCopy, cost, mem, stack, contract, in.evm.depth, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
// The Interpreter main run loop (contextual). This loop runs until either an
|
// The Interpreter main run loop (contextual). This loop runs until either an
|
||||||
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
|
||||||
// the execution of one of the operations or until the done flag is set by the
|
// the execution of one of the operations or until the done flag is set by the
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ func (s *StructLog) OpName() string {
|
||||||
// Note that reference types are actual VM data structures; make copies
|
// Note that reference types are actual VM data structures; make copies
|
||||||
// if you need to retain them beyond the current call.
|
// if you need to retain them beyond the current call.
|
||||||
type Tracer interface {
|
type Tracer interface {
|
||||||
|
CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) error
|
||||||
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
|
CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
|
||||||
|
CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error
|
||||||
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
|
CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +113,10 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CaptureState logs a new structured log message and pushes it out to the environment
|
// CaptureState logs a new structured log message and pushes it out to the environment
|
||||||
//
|
//
|
||||||
// CaptureState also tracks SSTORE ops to track dirty values.
|
// CaptureState also tracks SSTORE ops to track dirty values.
|
||||||
|
|
@ -161,6 +167,10 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *StructLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, memory *Memory, stack *Stack, contract *Contract, depth int, err error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) error {
|
||||||
fmt.Printf("0x%x", output)
|
fmt.Printf("0x%x", output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ The client's UI uses [React][React] with JSX syntax, which is validated by the [
|
||||||
|
|
||||||
### Development and bundling
|
### Development and bundling
|
||||||
|
|
||||||
As the dashboard depends on certain NPM packages (which are not included in the go-ethereum repo), these need to be installed first:
|
As the dashboard depends on certain NPM packages (which are not included in the `go-ethereum` repo), these need to be installed first:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ (cd dashboard/assets && npm install)
|
$ (cd dashboard/assets && npm install)
|
||||||
|
$ (cd dashboard/assets && ./node_modules/.bin/flow-typed install)
|
||||||
```
|
```
|
||||||
|
|
||||||
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources:
|
Normally the dashboard assets are bundled into Geth via `go-bindata` to avoid external dependencies. Rebuilding Geth after each UI modification however is not feasible from a developer perspective. Instead, we can run `webpack` in watch mode to automatically rebundle the UI, and ask `geth` to use external assets to not rely on compiled resources:
|
||||||
|
|
@ -22,13 +23,20 @@ $ (cd dashboard/assets && ./node_modules/.bin/webpack --watch)
|
||||||
$ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5
|
$ geth --dashboard --dashboard.assets=dashboard/assets/public --vmodule=dashboard=5
|
||||||
```
|
```
|
||||||
|
|
||||||
To bundle up the final UI into Geth, run `webpack` and `go generate`:
|
To bundle up the final UI into Geth, run `go generate`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ (cd dashboard/assets && ./node_modules/.bin/webpack)
|
|
||||||
$ go generate ./dashboard
|
$ go generate ./dashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Static type checking
|
||||||
|
|
||||||
|
Since JavaScript doesn't provide type safety, [Flow][Flow] is used to check types. These are only useful during development, so at the end of the process Babel will strip them.
|
||||||
|
|
||||||
|
To take advantage of static type checking, your IDE needs to be prepared for it. In case of [Atom][Atom] a configuration guide can be found [here][Atom config]: Install the [Nuclide][Nuclide] package for Flow support, making sure it installs all of its support packages by enabling `Install Recommended Packages on Startup`, and set the path of the `flow-bin` which were installed previously by `npm`.
|
||||||
|
|
||||||
|
For more IDE support install the `linter-eslint` package too, which finds the `.eslintrc` file, and provides real-time linting. Atom warns, that these two packages are incompatible, but they seem to work well together. For third-party library errors and auto-completion [flow-typed][flow-typed] is used.
|
||||||
|
|
||||||
### Have fun
|
### Have fun
|
||||||
|
|
||||||
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
|
[Webpack][Webpack] offers handy tools for visualizing the bundle's dependency tree and space usage.
|
||||||
|
|
@ -44,3 +52,8 @@ $ go generate ./dashboard
|
||||||
[WA]: http://webpack.github.io/analyse/
|
[WA]: http://webpack.github.io/analyse/
|
||||||
[WV]: http://chrisbateman.github.io/webpack-visualizer/
|
[WV]: http://chrisbateman.github.io/webpack-visualizer/
|
||||||
[Node.js]: https://nodejs.org/en/
|
[Node.js]: https://nodejs.org/en/
|
||||||
|
[Flow]: https://flow.org/
|
||||||
|
[Atom]: https://atom.io/
|
||||||
|
[Atom config]: https://medium.com/@fastphrase/integrating-flow-into-a-react-project-fbbc2f130eed
|
||||||
|
[Nuclide]: https://nuclide.io/docs/quick-start/getting-started/
|
||||||
|
[flow-typed]: https://github.com/flowtype/flow-typed
|
||||||
|
|
|
||||||
42113
dashboard/assets.go
42113
dashboard/assets.go
File diff suppressed because one or more lines are too long
|
|
@ -16,37 +16,68 @@
|
||||||
|
|
||||||
// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
|
// React syntax style mostly according to https://github.com/airbnb/javascript/tree/master/react
|
||||||
{
|
{
|
||||||
"plugins": [
|
'env': {
|
||||||
"react"
|
'browser': true,
|
||||||
],
|
'node': true,
|
||||||
"parser": "babel-eslint",
|
'es6': true,
|
||||||
"parserOptions": {
|
},
|
||||||
"ecmaFeatures": {
|
'parser': 'babel-eslint',
|
||||||
"jsx": true,
|
'parserOptions': {
|
||||||
"modules": true
|
'sourceType': 'module',
|
||||||
}
|
'ecmaVersion': 6,
|
||||||
},
|
'ecmaFeatures': {
|
||||||
"rules": {
|
'jsx': true,
|
||||||
"react/prefer-es6-class": 2,
|
}
|
||||||
"react/prefer-stateless-function": 2,
|
},
|
||||||
"react/jsx-pascal-case": 2,
|
'extends': 'airbnb',
|
||||||
"react/jsx-closing-bracket-location": [1, {"selfClosing": "tag-aligned", "nonEmpty": "tag-aligned"}],
|
'plugins': [
|
||||||
"react/jsx-closing-tag-location": 1,
|
'flowtype',
|
||||||
"jsx-quotes": ["error", "prefer-double"],
|
'react',
|
||||||
"no-multi-spaces": "error",
|
],
|
||||||
"react/jsx-tag-spacing": 2,
|
'rules': {
|
||||||
"react/jsx-curly-spacing": [2, {"when": "never", "children": true}],
|
'no-tabs': 'off',
|
||||||
"react/jsx-boolean-value": 2,
|
'indent': ['error', 'tab'],
|
||||||
"react/no-string-refs": 2,
|
'react/jsx-indent': ['error', 'tab'],
|
||||||
"react/jsx-wrap-multilines": 2,
|
'react/jsx-indent-props': ['error', 'tab'],
|
||||||
"react/self-closing-comp": 2,
|
'react/prefer-stateless-function': 'off',
|
||||||
"react/jsx-no-bind": 2,
|
|
||||||
"react/require-render-return": 2,
|
// Specifies the maximum length of a line.
|
||||||
"react/no-is-mounted": 2,
|
'max-len': ['warn', 120, 2, {
|
||||||
"key-spacing": ["error", {"align": {
|
'ignoreUrls': true,
|
||||||
"beforeColon": false,
|
'ignoreComments': false,
|
||||||
"afterColon": true,
|
'ignoreRegExpLiterals': true,
|
||||||
"on": "value"
|
'ignoreStrings': true,
|
||||||
}}]
|
'ignoreTemplateLiterals': true,
|
||||||
}
|
}],
|
||||||
|
// Enforces spacing between keys and values in object literal properties.
|
||||||
|
'key-spacing': ['error', {'align': {
|
||||||
|
'beforeColon': false,
|
||||||
|
'afterColon': true,
|
||||||
|
'on': 'value'
|
||||||
|
}}],
|
||||||
|
// Prohibits padding inside curly braces.
|
||||||
|
'object-curly-spacing': ['error', 'never'],
|
||||||
|
'no-use-before-define': 'off', // messageAPI
|
||||||
|
'default-case': 'off',
|
||||||
|
|
||||||
|
'flowtype/boolean-style': ['error', 'boolean'],
|
||||||
|
'flowtype/define-flow-type': 'warn',
|
||||||
|
'flowtype/generic-spacing': ['error', 'never'],
|
||||||
|
'flowtype/no-primitive-constructor-types': 'error',
|
||||||
|
'flowtype/no-weak-types': 'error',
|
||||||
|
'flowtype/object-type-delimiter': ['error', 'comma'],
|
||||||
|
'flowtype/require-valid-file-annotation': 'error',
|
||||||
|
'flowtype/semi': ['error', 'always'],
|
||||||
|
'flowtype/space-after-type-colon': ['error', 'always'],
|
||||||
|
'flowtype/space-before-generic-bracket': ['error', 'never'],
|
||||||
|
'flowtype/space-before-type-colon': ['error', 'never'],
|
||||||
|
'flowtype/union-intersection-spacing': ['error', 'always'],
|
||||||
|
'flowtype/use-flow-type': 'warn',
|
||||||
|
'flowtype/valid-syntax': 'warn',
|
||||||
|
},
|
||||||
|
'settings': {
|
||||||
|
'flowtype': {
|
||||||
|
'onlyFilesWithFlowAnnotation': true,
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
dashboard/assets/.flowconfig
Normal file
9
dashboard/assets/.flowconfig
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
[ignore]
|
||||||
|
<PROJECT_ROOT>/node_modules/material-ui/.*\.js\.flow
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
<PROJECT_ROOT>/flow-typed/
|
||||||
|
node_modules/jss/flow-typed
|
||||||
|
|
||||||
|
[options]
|
||||||
|
include_warnings=true
|
||||||
64
dashboard/assets/components/Body.jsx
Normal file
64
dashboard/assets/components/Body.jsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
|
||||||
|
import SideBar from './SideBar';
|
||||||
|
import Main from './Main';
|
||||||
|
import type {Content} from '../types/content';
|
||||||
|
|
||||||
|
// Styles for the Body component.
|
||||||
|
const styles = () => ({
|
||||||
|
body: {
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
opened: boolean,
|
||||||
|
changeContent: () => {},
|
||||||
|
active: string,
|
||||||
|
content: Content,
|
||||||
|
shouldUpdate: Object,
|
||||||
|
};
|
||||||
|
// Body renders the body of the dashboard.
|
||||||
|
class Body extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.body}>
|
||||||
|
<SideBar
|
||||||
|
opened={this.props.opened}
|
||||||
|
changeContent={this.props.changeContent}
|
||||||
|
/>
|
||||||
|
<Main
|
||||||
|
active={this.props.active}
|
||||||
|
content={this.props.content}
|
||||||
|
shouldUpdate={this.props.shouldUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(Body);
|
||||||
49
dashboard/assets/components/ChartGrid.jsx
Normal file
49
dashboard/assets/components/ChartGrid.jsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, {Component} from 'react';
|
||||||
|
import type {Node} from 'react';
|
||||||
|
|
||||||
|
import Grid from 'material-ui/Grid';
|
||||||
|
import {ResponsiveContainer} from 'recharts';
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
spacing: number,
|
||||||
|
children: Node,
|
||||||
|
};
|
||||||
|
// ChartGrid renders a grid container for responsive charts.
|
||||||
|
// The children are Recharts components extended with the Material-UI's xs property.
|
||||||
|
class ChartGrid extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={this.props.spacing}>
|
||||||
|
{
|
||||||
|
React.Children.map(this.props.children, child => (
|
||||||
|
<Grid item xs={child.props.xs}>
|
||||||
|
<ResponsiveContainer width="100%" height={child.props.height}>
|
||||||
|
{React.cloneElement(child, {data: child.props.values.map(value => ({value}))})}
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Grid>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChartGrid;
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -14,39 +16,78 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// isNullOrUndefined returns true if the given variable is null or undefined.
|
type ProvidedMenuProp = {|title: string, icon: string|};
|
||||||
export const isNullOrUndefined = variable => variable === null || typeof variable === 'undefined';
|
const menuSkeletons: Array<{|id: string, menu: ProvidedMenuProp|}> = [
|
||||||
|
{
|
||||||
export const LIMIT = {
|
id: 'home',
|
||||||
memory: 200, // Maximum number of memory data samples.
|
menu: {
|
||||||
traffic: 200, // Maximum number of traffic data samples.
|
title: 'Home',
|
||||||
log: 200, // Maximum number of logs.
|
icon: 'home',
|
||||||
};
|
},
|
||||||
|
}, {
|
||||||
|
id: 'chain',
|
||||||
|
menu: {
|
||||||
|
title: 'Chain',
|
||||||
|
icon: 'link',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'txpool',
|
||||||
|
menu: {
|
||||||
|
title: 'TxPool',
|
||||||
|
icon: 'credit-card',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'network',
|
||||||
|
menu: {
|
||||||
|
title: 'Network',
|
||||||
|
icon: 'globe',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'system',
|
||||||
|
menu: {
|
||||||
|
title: 'System',
|
||||||
|
icon: 'tachometer',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'logs',
|
||||||
|
menu: {
|
||||||
|
title: 'Logs',
|
||||||
|
icon: 'list',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export type MenuProp = {|...ProvidedMenuProp, id: string|};
|
||||||
// The sidebar menu and the main content are rendered based on these elements.
|
// The sidebar menu and the main content are rendered based on these elements.
|
||||||
export const TAGS = (() => {
|
// Using the id is circumstantial in some cases, so it is better to insert it also as a value.
|
||||||
const T = {
|
// This way the mistyping is prevented.
|
||||||
home: { title: "Home", },
|
export const MENU: Map<string, {...MenuProp}> = new Map(menuSkeletons.map(({id, menu}) => ([id, {id, ...menu}])));
|
||||||
chain: { title: "Chain", },
|
|
||||||
transactions: { title: "Transactions", },
|
|
||||||
network: { title: "Network", },
|
|
||||||
system: { title: "System", },
|
|
||||||
logs: { title: "Logs", },
|
|
||||||
};
|
|
||||||
// Using the key is circumstantial in some cases, so it is better to insert it also as a value.
|
|
||||||
// This way the mistyping is prevented.
|
|
||||||
for(let key in T) {
|
|
||||||
T[key]['id'] = key;
|
|
||||||
}
|
|
||||||
return T;
|
|
||||||
})();
|
|
||||||
|
|
||||||
export const DATA_KEYS = (() => {
|
type ProvidedSampleProp = {|limit: number|};
|
||||||
const DK = {};
|
const sampleSkeletons: Array<{|id: string, sample: ProvidedSampleProp|}> = [
|
||||||
["memory", "traffic", "logs"].map(key => {
|
{
|
||||||
DK[key] = key;
|
id: 'memory',
|
||||||
});
|
sample: {
|
||||||
return DK;
|
limit: 200,
|
||||||
})();
|
},
|
||||||
|
}, {
|
||||||
|
id: 'traffic',
|
||||||
|
sample: {
|
||||||
|
limit: 200,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
id: 'logs',
|
||||||
|
sample: {
|
||||||
|
limit: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export type SampleProp = {|...ProvidedSampleProp, id: string|};
|
||||||
|
export const SAMPLE: Map<string, {...SampleProp}> = new Map(sampleSkeletons.map(({id, sample}) => ([id, {id, ...sample}])));
|
||||||
|
|
||||||
// Temporary - taken from Material-UI
|
export const DURATION = 200;
|
||||||
export const DRAWER_WIDTH = 240;
|
|
||||||
|
export const LENS: Map<string, string> = new Map([
|
||||||
|
'content',
|
||||||
|
...menuSkeletons.map(({id}) => id),
|
||||||
|
...sampleSkeletons.map(({id}) => id),
|
||||||
|
].map(lens => [lens, lens]));
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,155 +17,183 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import SideBar from './SideBar.jsx';
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
import Header from './Header.jsx';
|
import {lensPath, view, set} from 'ramda';
|
||||||
import Main from "./Main.jsx";
|
|
||||||
import {isNullOrUndefined, LIMIT, TAGS, DATA_KEYS,} from "./Common.jsx";
|
|
||||||
|
|
||||||
// Styles for the Dashboard component.
|
import Header from './Header';
|
||||||
|
import Body from './Body';
|
||||||
|
import {MENU, SAMPLE} from './Common';
|
||||||
|
import type {Message, HomeMessage, LogsMessage, Chart} from '../types/message';
|
||||||
|
import type {Content} from '../types/content';
|
||||||
|
|
||||||
|
// appender appends an array (A) to the end of another array (B) in the state.
|
||||||
|
// lens is the path of B in the state, samples is A, and limit is the maximum size of the changed array.
|
||||||
|
//
|
||||||
|
// appender retrieves a function, which overrides the state's value at lens, and returns with the overridden state.
|
||||||
|
const appender = (lens, samples, limit) => (state) => {
|
||||||
|
const newSamples = [
|
||||||
|
...view(lens, state), // retrieves a specific value of the state at the given path (lens).
|
||||||
|
...samples,
|
||||||
|
];
|
||||||
|
// set is a function of ramda.js, which needs the path, the new value, the original state, and retrieves
|
||||||
|
// the altered state.
|
||||||
|
return set(
|
||||||
|
lens,
|
||||||
|
newSamples.slice(newSamples.length > limit ? newSamples.length - limit : 0),
|
||||||
|
state
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Lenses for specific data fields in the state, used for a clearer deep update.
|
||||||
|
// NOTE: This solution will be changed very likely.
|
||||||
|
const memoryLens = lensPath(['content', 'home', 'memory']);
|
||||||
|
const trafficLens = lensPath(['content', 'home', 'traffic']);
|
||||||
|
const logLens = lensPath(['content', 'logs', 'log']);
|
||||||
|
// styles retrieves the styles for the Dashboard component.
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
appFrame: {
|
dashboard: {
|
||||||
position: 'relative',
|
display: 'flex',
|
||||||
display: 'flex',
|
flexFlow: 'column',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
background: theme.palette.background.default,
|
background: theme.palette.background.default,
|
||||||
},
|
zIndex: 1,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
};
|
||||||
|
type State = {
|
||||||
|
active: string, // active menu
|
||||||
|
sideBar: boolean, // true if the sidebar is opened
|
||||||
|
content: $Shape<Content>, // the visualized data
|
||||||
|
shouldUpdate: Set<string> // labels for the components, which need to rerender based on the incoming message
|
||||||
|
};
|
||||||
|
// Dashboard is the main component, which renders the whole page, makes connection with the server and
|
||||||
|
// listens for messages. When there is an incoming message, updates the page's content correspondingly.
|
||||||
|
class Dashboard extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
active: MENU.get('home').id,
|
||||||
|
sideBar: true,
|
||||||
|
content: {home: {memory: [], traffic: []}, logs: {log: []}},
|
||||||
|
shouldUpdate: new Set(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Dashboard is the main component, which renders the whole page, makes connection with the server and listens for messages.
|
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
|
||||||
// When there is an incoming message, updates the page's content correspondingly.
|
componentDidMount() {
|
||||||
class Dashboard extends Component {
|
this.reconnect();
|
||||||
constructor(props) {
|
}
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
active: TAGS.home.id, // active menu
|
|
||||||
sideBar: true, // true if the sidebar is opened
|
|
||||||
memory: [],
|
|
||||||
traffic: [],
|
|
||||||
logs: [],
|
|
||||||
shouldUpdate: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
|
// reconnect establishes a websocket connection with the server, listens for incoming messages
|
||||||
componentDidMount() {
|
// and tries to reconnect on connection loss.
|
||||||
this.reconnect();
|
reconnect = () => {
|
||||||
}
|
this.setState({
|
||||||
|
content: {home: {memory: [], traffic: []}, logs: {log: []}},
|
||||||
|
});
|
||||||
|
const server = new WebSocket(`${((window.location.protocol === 'https:') ? 'wss://' : 'ws://') + window.location.host}/api`);
|
||||||
|
server.onmessage = (event) => {
|
||||||
|
const msg: Message = JSON.parse(event.data);
|
||||||
|
if (!msg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.update(msg);
|
||||||
|
};
|
||||||
|
server.onclose = () => {
|
||||||
|
setTimeout(this.reconnect, 3000);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// reconnect establishes a websocket connection with the server, listens for incoming messages
|
// samples retrieves the raw data of a chart field from the incoming message.
|
||||||
// and tries to reconnect on connection loss.
|
samples = (chart: Chart) => {
|
||||||
reconnect = () => {
|
let s = [];
|
||||||
const server = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/api");
|
if (chart.history) {
|
||||||
|
s = chart.history.map(({value}) => (value || 0)); // traffic comes without value at the beginning
|
||||||
|
}
|
||||||
|
if (chart.new) {
|
||||||
|
s = [...s, chart.new.value || 0];
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
server.onmessage = event => {
|
// handleHome changes the home-menu related part of the state.
|
||||||
const msg = JSON.parse(event.data);
|
handleHome = (home: HomeMessage) => {
|
||||||
if (isNullOrUndefined(msg)) {
|
this.setState((prevState) => {
|
||||||
return;
|
let newState = prevState;
|
||||||
}
|
newState.shouldUpdate = new Set();
|
||||||
this.update(msg);
|
if (home.memory) {
|
||||||
};
|
newState = appender(memoryLens, this.samples(home.memory), SAMPLE.get('memory').limit)(newState);
|
||||||
|
newState.shouldUpdate.add('memory');
|
||||||
|
}
|
||||||
|
if (home.traffic) {
|
||||||
|
newState = appender(trafficLens, this.samples(home.traffic), SAMPLE.get('traffic').limit)(newState);
|
||||||
|
newState.shouldUpdate.add('traffic');
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
server.onclose = () => {
|
// handleLogs changes the logs-menu related part of the state.
|
||||||
setTimeout(this.reconnect, 3000);
|
handleLogs = (logs: LogsMessage) => {
|
||||||
};
|
this.setState((prevState) => {
|
||||||
};
|
let newState = prevState;
|
||||||
|
newState.shouldUpdate = new Set();
|
||||||
|
if (logs.log) {
|
||||||
|
newState = appender(logLens, [logs.log], SAMPLE.get('logs').limit)(newState);
|
||||||
|
newState.shouldUpdate.add('logs');
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// update analyzes the incoming message, and updates the charts' content correspondingly.
|
// update analyzes the incoming message, and updates the charts' content correspondingly.
|
||||||
update = msg => {
|
update = (msg: Message) => {
|
||||||
console.log(msg);
|
if (msg.home) {
|
||||||
this.setState(prevState => {
|
this.handleHome(msg.home);
|
||||||
let newState = [];
|
}
|
||||||
newState.shouldUpdate = {};
|
if (msg.logs) {
|
||||||
const insert = (key, values, limit) => {
|
this.handleLogs(msg.logs);
|
||||||
newState[key] = [...prevState[key], ...values];
|
}
|
||||||
while (newState[key].length > limit) {
|
};
|
||||||
newState[key].shift();
|
|
||||||
}
|
|
||||||
newState.shouldUpdate[key] = true;
|
|
||||||
};
|
|
||||||
// (Re)initialize the state with the past data.
|
|
||||||
if (!isNullOrUndefined(msg.history)) {
|
|
||||||
const memory = DATA_KEYS.memory;
|
|
||||||
const traffic = DATA_KEYS.traffic;
|
|
||||||
newState[memory] = [];
|
|
||||||
newState[traffic] = [];
|
|
||||||
if (!isNullOrUndefined(msg.history.memorySamples)) {
|
|
||||||
newState[memory] = msg.history.memorySamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
|
|
||||||
while (newState[memory].length > LIMIT.memory) {
|
|
||||||
newState[memory].shift();
|
|
||||||
}
|
|
||||||
newState.shouldUpdate[memory] = true;
|
|
||||||
}
|
|
||||||
if (!isNullOrUndefined(msg.history.trafficSamples)) {
|
|
||||||
newState[traffic] = msg.history.trafficSamples.map(elem => isNullOrUndefined(elem.value) ? 0 : elem.value);
|
|
||||||
while (newState[traffic].length > LIMIT.traffic) {
|
|
||||||
newState[traffic].shift();
|
|
||||||
}
|
|
||||||
newState.shouldUpdate[traffic] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Insert the new data samples.
|
|
||||||
if (!isNullOrUndefined(msg.memory)) {
|
|
||||||
insert(DATA_KEYS.memory, [isNullOrUndefined(msg.memory.value) ? 0 : msg.memory.value], LIMIT.memory);
|
|
||||||
}
|
|
||||||
if (!isNullOrUndefined(msg.traffic)) {
|
|
||||||
insert(DATA_KEYS.traffic, [isNullOrUndefined(msg.traffic.value) ? 0 : msg.traffic.value], LIMIT.traffic);
|
|
||||||
}
|
|
||||||
if (!isNullOrUndefined(msg.log)) {
|
|
||||||
insert(DATA_KEYS.logs, [msg.log], LIMIT.log);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newState;
|
// changeContent sets the active label, which is used at the content rendering.
|
||||||
});
|
changeContent = (newActive: string) => {
|
||||||
};
|
this.setState(prevState => (prevState.active !== newActive ? {active: newActive} : {}));
|
||||||
|
};
|
||||||
|
|
||||||
// The change of the active label on the SideBar component will trigger a new render in the Main component.
|
// openSideBar opens the sidebar.
|
||||||
changeContent = active => {
|
openSideBar = () => {
|
||||||
this.setState(prevState => prevState.active !== active ? {active: active} : {});
|
this.setState({sideBar: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
openSideBar = () => {
|
// closeSideBar closes the sidebar.
|
||||||
this.setState({sideBar: true});
|
closeSideBar = () => {
|
||||||
};
|
this.setState({sideBar: false});
|
||||||
|
};
|
||||||
|
|
||||||
closeSideBar = () => {
|
render() {
|
||||||
this.setState({sideBar: false});
|
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
// The classes property is injected by withStyles().
|
<div className={classes.dashboard}>
|
||||||
const {classes} = this.props;
|
<Header
|
||||||
|
opened={this.state.sideBar}
|
||||||
return (
|
openSideBar={this.openSideBar}
|
||||||
<div className={classes.appFrame}>
|
closeSideBar={this.closeSideBar}
|
||||||
<Header
|
/>
|
||||||
opened={this.state.sideBar}
|
<Body
|
||||||
open={this.openSideBar}
|
opened={this.state.sideBar}
|
||||||
/>
|
changeContent={this.changeContent}
|
||||||
<SideBar
|
active={this.state.active}
|
||||||
opened={this.state.sideBar}
|
content={this.state.content}
|
||||||
close={this.closeSideBar}
|
shouldUpdate={this.state.shouldUpdate}
|
||||||
changeContent={this.changeContent}
|
/>
|
||||||
/>
|
</div>
|
||||||
<Main
|
);
|
||||||
opened={this.state.sideBar}
|
}
|
||||||
active={this.state.active}
|
|
||||||
memory={this.state.memory}
|
|
||||||
traffic={this.state.traffic}
|
|
||||||
logs={this.state.logs}
|
|
||||||
shouldUpdate={this.state.shouldUpdate}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Dashboard.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(Dashboard);
|
export default withStyles(styles)(Dashboard);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,73 +17,89 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
import AppBar from 'material-ui/AppBar';
|
import AppBar from 'material-ui/AppBar';
|
||||||
import Toolbar from 'material-ui/Toolbar';
|
import Toolbar from 'material-ui/Toolbar';
|
||||||
import Typography from 'material-ui/Typography';
|
import Transition from 'react-transition-group/Transition';
|
||||||
import IconButton from 'material-ui/IconButton';
|
import IconButton from 'material-ui/IconButton';
|
||||||
import MenuIcon from 'material-ui-icons/Menu';
|
import Typography from 'material-ui/Typography';
|
||||||
|
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
|
||||||
|
|
||||||
import {DRAWER_WIDTH} from './Common.jsx';
|
import {DURATION} from './Common';
|
||||||
|
|
||||||
|
// arrowDefault is the default style of the arrow button.
|
||||||
|
const arrowDefault = {
|
||||||
|
transition: `transform ${DURATION}ms`,
|
||||||
|
};
|
||||||
|
// arrowTransition is the additional style of the arrow button corresponding to the transition's state.
|
||||||
|
const arrowTransition = {
|
||||||
|
entered: {transform: 'rotate(180deg)'},
|
||||||
|
};
|
||||||
// Styles for the Header component.
|
// Styles for the Header component.
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
appBar: {
|
header: {
|
||||||
position: 'absolute',
|
backgroundColor: theme.palette.background.appBar,
|
||||||
transition: theme.transitions.create(['margin', 'width'], {
|
color: theme.palette.getContrastText(theme.palette.background.appBar),
|
||||||
easing: theme.transitions.easing.sharp,
|
zIndex: theme.zIndex.appBar,
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
},
|
||||||
}),
|
toolbar: {
|
||||||
},
|
paddingLeft: theme.spacing.unit,
|
||||||
appBarShift: {
|
paddingRight: theme.spacing.unit,
|
||||||
marginLeft: DRAWER_WIDTH,
|
},
|
||||||
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
mainText: {
|
||||||
transition: theme.transitions.create(['margin', 'width'], {
|
paddingLeft: theme.spacing.unit,
|
||||||
easing: theme.transitions.easing.easeOut,
|
},
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
menuButton: {
|
|
||||||
marginLeft: 12,
|
|
||||||
marginRight: 20,
|
|
||||||
},
|
|
||||||
hide: {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
opened: boolean,
|
||||||
|
openSideBar: () => {},
|
||||||
|
closeSideBar: () => {},
|
||||||
|
};
|
||||||
|
// Header renders the header of the dashboard.
|
||||||
|
class Header extends Component<Props> {
|
||||||
|
shouldComponentUpdate(nextProps) {
|
||||||
|
return nextProps.opened !== this.props.opened;
|
||||||
|
}
|
||||||
|
|
||||||
// Header renders a header, which contains a sidebar opener icon when that is closed.
|
// changeSideBar opens or closes the sidebar corresponding to the previous state.
|
||||||
class Header extends Component {
|
changeSideBar = () => {
|
||||||
render() {
|
if (this.props.opened) {
|
||||||
// The classes property is injected by withStyles().
|
this.props.closeSideBar();
|
||||||
const {classes} = this.props;
|
} else {
|
||||||
|
this.props.openSideBar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
// arrowButton is connected to the sidebar; changes its state.
|
||||||
<AppBar className={classNames(classes.appBar, this.props.opened && classes.appBarShift)}>
|
arrowButton = (transitionState: string) => (
|
||||||
<Toolbar disableGutters={!this.props.opened}>
|
<IconButton onClick={this.changeSideBar}>
|
||||||
<IconButton
|
<ChevronLeftIcon
|
||||||
color="contrast"
|
style={{
|
||||||
aria-label="open drawer"
|
...arrowDefault,
|
||||||
onClick={this.props.open}
|
...arrowTransition[transitionState],
|
||||||
className={classNames(classes.menuButton, this.props.opened && classes.hide)}
|
}}
|
||||||
>
|
/>
|
||||||
<MenuIcon />
|
</IconButton>
|
||||||
</IconButton>
|
);
|
||||||
<Typography type="title" color="inherit" noWrap>
|
|
||||||
Go Ethereum Dashboard
|
render() {
|
||||||
</Typography>
|
const {classes, opened} = this.props; // The classes property is injected by withStyles().
|
||||||
</Toolbar>
|
|
||||||
</AppBar>
|
return (
|
||||||
);
|
<AppBar position="static" className={classes.header}>
|
||||||
}
|
<Toolbar className={classes.toolbar}>
|
||||||
|
<Transition mountOnEnter in={opened} timeout={{enter: DURATION}}>
|
||||||
|
{this.arrowButton}
|
||||||
|
</Transition>
|
||||||
|
<Typography type="title" color="inherit" noWrap className={classes.mainText}>
|
||||||
|
Go Ethereum Dashboard
|
||||||
|
</Typography>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
opened: PropTypes.bool.isRequired,
|
|
||||||
open: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(Header);
|
export default withStyles(styles)(Header);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,75 +17,56 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Grid from 'material-ui/Grid';
|
|
||||||
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line, ResponsiveContainer} from 'recharts';
|
|
||||||
import {withTheme} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import {isNullOrUndefined, DATA_KEYS} from "./Common.jsx";
|
import withTheme from 'material-ui/styles/withTheme';
|
||||||
|
import {LineChart, AreaChart, Area, YAxis, CartesianGrid, Line} from 'recharts';
|
||||||
|
|
||||||
// ChartGrid renders a grid container for responsive charts.
|
import ChartGrid from './ChartGrid';
|
||||||
// The children are Recharts components extended with the Material-UI's xs property.
|
import type {ChartEntry} from '../types/message';
|
||||||
class ChartGrid extends Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Grid container spacing={this.props.spacing}>
|
|
||||||
{
|
|
||||||
React.Children.map(this.props.children, child => (
|
|
||||||
<Grid item xs={child.props.xs}>
|
|
||||||
<ResponsiveContainer width="100%" height={child.props.height}>
|
|
||||||
{React.cloneElement(child, {data: child.props.values.map(value => ({value: value}))})}
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</Grid>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ChartGrid.propTypes = {
|
export type Props = {
|
||||||
spacing: PropTypes.number.isRequired,
|
theme: Object,
|
||||||
|
memory: Array<ChartEntry>,
|
||||||
|
traffic: Array<ChartEntry>,
|
||||||
|
shouldUpdate: Object,
|
||||||
};
|
};
|
||||||
|
// Home renders the home content.
|
||||||
|
class Home extends Component<Props> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
const {theme} = props; // The theme property is injected by withTheme().
|
||||||
|
this.memoryColor = theme.palette.primary[300];
|
||||||
|
this.trafficColor = theme.palette.secondary[300];
|
||||||
|
}
|
||||||
|
|
||||||
// Home renders the home component.
|
shouldComponentUpdate(nextProps) {
|
||||||
class Home extends Component {
|
return nextProps.shouldUpdate.has('memory') || nextProps.shouldUpdate.has('traffic');
|
||||||
shouldComponentUpdate(nextProps) {
|
}
|
||||||
return !isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.memory]) ||
|
|
||||||
!isNullOrUndefined(nextProps.shouldUpdate[DATA_KEYS.traffic]);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {theme} = this.props;
|
const {memory, traffic} = this.props;
|
||||||
const memoryColor = theme.palette.primary[300];
|
|
||||||
const trafficColor = theme.palette.secondary[300];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChartGrid spacing={24}>
|
<ChartGrid spacing={24}>
|
||||||
<AreaChart xs={6} height={300} values={this.props.memory}>
|
<AreaChart xs={6} height={300} values={memory}>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<Area type="monotone" dataKey="value" stroke={memoryColor} fill={memoryColor} />
|
<Area type="monotone" dataKey="value" stroke={this.memoryColor} fill={this.memoryColor} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
<LineChart xs={6} height={300} values={this.props.traffic}>
|
<LineChart xs={6} height={300} values={traffic}>
|
||||||
<Line type="monotone" dataKey="value" stroke={trafficColor} dot={false} />
|
<Line type="monotone" dataKey="value" stroke={this.trafficColor} dot={false} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
<LineChart xs={6} height={300} values={this.props.memory}>
|
<LineChart xs={6} height={300} values={memory}>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
<CartesianGrid stroke="#eee" strokeDasharray="5 5" />
|
||||||
<Line type="monotone" dataKey="value" stroke={memoryColor} dot={false} />
|
<Line type="monotone" dataKey="value" stroke={this.memoryColor} dot={false} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
<AreaChart xs={6} height={300} values={this.props.traffic}>
|
<AreaChart xs={6} height={300} values={traffic}>
|
||||||
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
|
<CartesianGrid stroke="#eee" strokeDasharray="5 5" vertical={false} />
|
||||||
<Area type="monotone" dataKey="value" stroke={trafficColor} fill={trafficColor} />
|
<Area type="monotone" dataKey="value" stroke={this.trafficColor} fill={this.trafficColor} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ChartGrid>
|
</ChartGrid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Home.propTypes = {
|
|
||||||
theme: PropTypes.object.isRequired,
|
|
||||||
shouldUpdate: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTheme()(Home);
|
export default withTheme()(Home);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,95 +17,52 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import {TAGS, DRAWER_WIDTH} from "./Common.jsx";
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
import Home from './Home.jsx';
|
|
||||||
|
|
||||||
// ContentSwitch chooses and renders the proper page content.
|
import Home from './Home';
|
||||||
class ContentSwitch extends Component {
|
import {MENU} from './Common';
|
||||||
render() {
|
import type {Content} from '../types/content';
|
||||||
switch(this.props.active) {
|
|
||||||
case TAGS.home.id:
|
|
||||||
return <Home memory={this.props.memory} traffic={this.props.traffic} shouldUpdate={this.props.shouldUpdate} />;
|
|
||||||
case TAGS.chain.id:
|
|
||||||
return null;
|
|
||||||
case TAGS.transactions.id:
|
|
||||||
return null;
|
|
||||||
case TAGS.network.id:
|
|
||||||
// Only for testing.
|
|
||||||
return null;
|
|
||||||
case TAGS.system.id:
|
|
||||||
return null;
|
|
||||||
case TAGS.logs.id:
|
|
||||||
return <div>{this.props.logs.map((log, index) => <div key={index}>{log}</div>)}</div>;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentSwitch.propTypes = {
|
// Styles for the Content component.
|
||||||
active: PropTypes.string.isRequired,
|
|
||||||
shouldUpdate: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
// styles contains the styles for the Main component.
|
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
content: {
|
content: {
|
||||||
width: '100%',
|
flexGrow: 1,
|
||||||
marginLeft: -DRAWER_WIDTH,
|
backgroundColor: theme.palette.background.default,
|
||||||
flexGrow: 1,
|
padding: theme.spacing.unit * 3,
|
||||||
backgroundColor: theme.palette.background.default,
|
overflow: 'auto',
|
||||||
padding: theme.spacing.unit * 3,
|
},
|
||||||
transition: theme.transitions.create('margin', {
|
|
||||||
easing: theme.transitions.easing.sharp,
|
|
||||||
duration: theme.transitions.duration.leavingScreen,
|
|
||||||
}),
|
|
||||||
marginTop: 56,
|
|
||||||
overflow: 'auto',
|
|
||||||
[theme.breakpoints.up('sm')]: {
|
|
||||||
content: {
|
|
||||||
height: 'calc(100% - 64px)',
|
|
||||||
marginTop: 64,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contentShift: {
|
|
||||||
marginLeft: 0,
|
|
||||||
transition: theme.transitions.create('margin', {
|
|
||||||
easing: theme.transitions.easing.easeOut,
|
|
||||||
duration: theme.transitions.duration.enteringScreen,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
// Main renders a component for the page content.
|
classes: Object,
|
||||||
class Main extends Component {
|
active: string,
|
||||||
render() {
|
content: Content,
|
||||||
// The classes property is injected by withStyles().
|
shouldUpdate: Object,
|
||||||
const {classes} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className={classNames(classes.content, this.props.opened && classes.contentShift)}>
|
|
||||||
<ContentSwitch
|
|
||||||
active={this.props.active}
|
|
||||||
memory={this.props.memory}
|
|
||||||
traffic={this.props.traffic}
|
|
||||||
logs={this.props.logs}
|
|
||||||
shouldUpdate={this.props.shouldUpdate}
|
|
||||||
/>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Main.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
opened: PropTypes.bool.isRequired,
|
|
||||||
active: PropTypes.string.isRequired,
|
|
||||||
shouldUpdate: PropTypes.object.isRequired,
|
|
||||||
};
|
};
|
||||||
|
// Main renders the chosen content.
|
||||||
|
class Main extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classes, active, content, shouldUpdate,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
let children = null;
|
||||||
|
switch (active) {
|
||||||
|
case MENU.get('home').id:
|
||||||
|
children = <Home memory={content.home.memory} traffic={content.home.traffic} shouldUpdate={shouldUpdate} />;
|
||||||
|
break;
|
||||||
|
case MENU.get('chain').id:
|
||||||
|
case MENU.get('txpool').id:
|
||||||
|
case MENU.get('network').id:
|
||||||
|
case MENU.get('system').id:
|
||||||
|
children = <div>Work in progress.</div>;
|
||||||
|
break;
|
||||||
|
case MENU.get('logs').id:
|
||||||
|
children = <div>{content.logs.log.map((log, index) => <div key={index}>{log}</div>)}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={classes.content}>{children}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default withStyles(styles)(Main);
|
export default withStyles(styles)(Main);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,92 +17,106 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import {withStyles} from 'material-ui/styles';
|
|
||||||
import Drawer from 'material-ui/Drawer';
|
|
||||||
import {IconButton} from "material-ui";
|
|
||||||
import List, {ListItem, ListItemText} from 'material-ui/List';
|
|
||||||
import ChevronLeftIcon from 'material-ui-icons/ChevronLeft';
|
|
||||||
|
|
||||||
import {TAGS, DRAWER_WIDTH} from './Common.jsx';
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import List, {ListItem, ListItemIcon, ListItemText} from 'material-ui/List';
|
||||||
|
import Icon from 'material-ui/Icon';
|
||||||
|
import Transition from 'react-transition-group/Transition';
|
||||||
|
import {Icon as FontAwesome} from 'react-fa';
|
||||||
|
|
||||||
|
import {MENU, DURATION} from './Common';
|
||||||
|
|
||||||
|
// menuDefault is the default style of the menu.
|
||||||
|
const menuDefault = {
|
||||||
|
transition: `margin-left ${DURATION}ms`,
|
||||||
|
};
|
||||||
|
// menuTransition is the additional style of the menu corresponding to the transition's state.
|
||||||
|
const menuTransition = {
|
||||||
|
entered: {marginLeft: -200},
|
||||||
|
};
|
||||||
// Styles for the SideBar component.
|
// Styles for the SideBar component.
|
||||||
const styles = theme => ({
|
const styles = theme => ({
|
||||||
drawerPaper: {
|
list: {
|
||||||
position: 'relative',
|
background: theme.palette.background.appBar,
|
||||||
height: '100%',
|
},
|
||||||
width: DRAWER_WIDTH,
|
listItem: {
|
||||||
},
|
minWidth: theme.spacing.unit * 3,
|
||||||
drawerHeader: {
|
},
|
||||||
display: 'flex',
|
icon: {
|
||||||
alignItems: 'center',
|
fontSize: theme.spacing.unit * 3,
|
||||||
justifyContent: 'flex-end',
|
},
|
||||||
padding: '0 8px',
|
|
||||||
...theme.mixins.toolbar,
|
|
||||||
transitionDuration: {
|
|
||||||
enter: theme.transitions.duration.enteringScreen,
|
|
||||||
exit: theme.transitions.duration.leavingScreen,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
export type Props = {
|
||||||
|
classes: Object,
|
||||||
|
opened: boolean,
|
||||||
|
changeContent: () => {},
|
||||||
|
};
|
||||||
|
// SideBar renders the sidebar of the dashboard.
|
||||||
|
class SideBar extends Component<Props> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
// SideBar renders a sidebar component.
|
// clickOn contains onClick event functions for the menu items.
|
||||||
class SideBar extends Component {
|
// Instantiate only once, and reuse the existing functions to prevent the creation of
|
||||||
constructor(props) {
|
// new function instances every time the render method is triggered.
|
||||||
super(props);
|
this.clickOn = {};
|
||||||
|
MENU.forEach((menu) => {
|
||||||
|
this.clickOn[menu.id] = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
props.changeContent(menu.id);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// clickOn contains onClick event functions for the menu items.
|
shouldComponentUpdate(nextProps) {
|
||||||
// Instantiate only once, and reuse the existing functions to prevent the creation of
|
return nextProps.opened !== this.props.opened;
|
||||||
// new function instances every time the render method is triggered.
|
}
|
||||||
this.clickOn = {};
|
|
||||||
for(let key in TAGS) {
|
|
||||||
const id = TAGS[key].id;
|
|
||||||
this.clickOn[id] = event => {
|
|
||||||
event.preventDefault();
|
|
||||||
console.log(event.target.key);
|
|
||||||
this.props.changeContent(id);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
menuItems = (transitionState) => {
|
||||||
// The classes property is injected by withStyles().
|
const {classes} = this.props;
|
||||||
const {classes} = this.props;
|
const children = [];
|
||||||
|
MENU.forEach((menu) => {
|
||||||
|
children.push(
|
||||||
|
<ListItem button key={menu.id} onClick={this.clickOn[menu.id]} className={classes.listItem}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon className={classes.icon}>
|
||||||
|
<FontAwesome name={menu.icon} />
|
||||||
|
</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={menu.title}
|
||||||
|
style={{
|
||||||
|
...menuDefault,
|
||||||
|
...menuTransition[transitionState],
|
||||||
|
padding: 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListItem>,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
// menu renders the list of the menu items.
|
||||||
<Drawer
|
menu = (transitionState) => {
|
||||||
type="persistent"
|
const {classes} = this.props; // The classes property is injected by withStyles().
|
||||||
classes={{paper: classes.drawerPaper,}}
|
|
||||||
open={this.props.opened}
|
return (
|
||||||
>
|
<div className={classes.list}>
|
||||||
<div>
|
<List>
|
||||||
<div className={classes.drawerHeader}>
|
{this.menuItems(transitionState)}
|
||||||
<IconButton onClick={this.props.close}>
|
</List>
|
||||||
<ChevronLeftIcon />
|
</div>
|
||||||
</IconButton>
|
);
|
||||||
</div>
|
};
|
||||||
<List>
|
|
||||||
{
|
render() {
|
||||||
Object.values(TAGS).map(tag => {
|
return (
|
||||||
return (
|
<Transition mountOnEnter in={this.props.opened} timeout={{enter: DURATION}}>
|
||||||
<ListItem button key={tag.id} onClick={this.clickOn[tag.id]}>
|
{this.menu}
|
||||||
<ListItemText primary={tag.title} />
|
</Transition>
|
||||||
</ListItem>
|
);
|
||||||
);
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
</List>
|
|
||||||
</div>
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SideBar.propTypes = {
|
|
||||||
classes: PropTypes.object.isRequired,
|
|
||||||
opened: PropTypes.bool.isRequired,
|
|
||||||
close: PropTypes.func.isRequired,
|
|
||||||
changeContent: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withStyles(styles)(SideBar);
|
export default withStyles(styles)(SideBar);
|
||||||
|
|
|
||||||
25
dashboard/assets/fa-only-woff-loader.js
Normal file
25
dashboard/assets/fa-only-woff-loader.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// fa-only-woff-loader removes the .eot, .ttf, .svg dependencies of the FontAwesome library,
|
||||||
|
// because they produce unused extra blobs.
|
||||||
|
module.exports = function(content) {
|
||||||
|
return content
|
||||||
|
.replace(/src.*url(?!.*url.*(\.eot)).*(\.eot)[^;]*;/,'')
|
||||||
|
.replace(/url(?!.*url.*(\.eot)).*(\.eot)[^,]*,/,'')
|
||||||
|
.replace(/url(?!.*url.*(\.ttf)).*(\.ttf)[^,]*,/,'')
|
||||||
|
.replace(/,[^,]*url(?!.*url.*(\.svg)).*(\.svg)[^;]*;/,';');
|
||||||
|
};
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
// Copyright 2017 The go-ethereum Authors
|
// Copyright 2017 The go-ethereum Authors
|
||||||
// This file is part of the go-ethereum library.
|
// This file is part of the go-ethereum library.
|
||||||
//
|
//
|
||||||
|
|
@ -15,22 +17,25 @@
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {hydrate} from 'react-dom';
|
import {render} from 'react-dom';
|
||||||
import {createMuiTheme, MuiThemeProvider} from 'material-ui/styles';
|
|
||||||
|
|
||||||
import Dashboard from './components/Dashboard.jsx';
|
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
|
||||||
|
import createMuiTheme from 'material-ui/styles/createMuiTheme';
|
||||||
|
|
||||||
|
import Dashboard from './components/Dashboard';
|
||||||
|
|
||||||
// Theme for the dashboard.
|
|
||||||
const theme = createMuiTheme({
|
const theme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
type: 'dark',
|
type: 'dark',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const dashboard = document.getElementById('dashboard');
|
||||||
// Renders the whole dashboard.
|
if (dashboard) {
|
||||||
hydrate(
|
// Renders the whole dashboard.
|
||||||
<MuiThemeProvider theme={theme}>
|
render(
|
||||||
<Dashboard />
|
<MuiThemeProvider theme={theme}>
|
||||||
</MuiThemeProvider>,
|
<Dashboard />
|
||||||
document.getElementById('dashboard')
|
</MuiThemeProvider>,
|
||||||
);
|
dashboard,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
6806
dashboard/assets/package-lock.json
generated
Normal file
6806
dashboard/assets/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,22 +1,41 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-eslint": "^8.0.1",
|
"babel-eslint": "^8.0.3",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||||
"babel-preset-stage-0": "^6.24.1",
|
"babel-plugin-transform-flow-strip-types": "^6.22.0",
|
||||||
"classnames": "^2.2.5",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"eslint": "^4.5.0",
|
"babel-preset-env": "^1.6.1",
|
||||||
"eslint-plugin-react": "^7.4.0",
|
"babel-preset-react": "^6.24.1",
|
||||||
"material-ui": "^1.0.0-beta.18",
|
"babel-preset-stage-0": "^6.24.1",
|
||||||
"material-ui-icons": "^1.0.0-beta.17",
|
"babel-runtime": "^6.26.0",
|
||||||
"path": "^0.12.7",
|
"classnames": "^2.2.5",
|
||||||
"prop-types": "^15.6.0",
|
"css-loader": "^0.28.7",
|
||||||
"recharts": "^1.0.0-beta.0",
|
"eslint": "^4.13.1",
|
||||||
"react": "^16.0.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"react-dom": "^16.0.0",
|
"eslint-loader": "^1.9.0",
|
||||||
"url": "^0.11.0",
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"webpack": "^3.5.5"
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
}
|
"eslint-plugin-react": "^7.5.1",
|
||||||
|
"eslint-plugin-flowtype": "^2.40.1",
|
||||||
|
"file-loader": "^1.1.6",
|
||||||
|
"flow-bin": "^0.61.0",
|
||||||
|
"flow-bin-loader": "^1.0.2",
|
||||||
|
"flow-typed": "^2.2.3",
|
||||||
|
"material-ui": "^1.0.0-beta.24",
|
||||||
|
"material-ui-icons": "^1.0.0-beta.17",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"ramda": "^0.25.0",
|
||||||
|
"react": "^16.2.0",
|
||||||
|
"react-dom": "^16.2.0",
|
||||||
|
"react-fa": "^5.0.0",
|
||||||
|
"react-transition-group": "^2.2.1",
|
||||||
|
"recharts": "^1.0.0-beta.6",
|
||||||
|
"style-loader": "^0.19.1",
|
||||||
|
"url": "^0.11.0",
|
||||||
|
"url-loader": "^0.6.2",
|
||||||
|
"webpack": "^3.10.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,15 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<title>Go Ethereum Dashboard</title>
|
<title>Go Ethereum Dashboard</title>
|
||||||
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico"/>
|
<link rel="shortcut icon" type="image/ico" href="https://ethereum.org/favicon.ico" />
|
||||||
|
<style>
|
||||||
<!-- TODO (kurkomisi): Return to the external libraries to speed up the bundling during development -->
|
::-webkit-scrollbar {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #212121;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="height: 100%; margin: 0">
|
<body style="height: 100%; margin: 0">
|
||||||
<div id="dashboard" style="height: 100%"></div>
|
<div id="dashboard" style="height: 100%"></div>
|
||||||
|
|
|
||||||
53
dashboard/assets/types/content.jsx
Normal file
53
dashboard/assets/types/content.jsx
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import type {ChartEntry} from './message';
|
||||||
|
|
||||||
|
export type Content = {
|
||||||
|
home: Home,
|
||||||
|
chain: Chain,
|
||||||
|
txpool: TxPool,
|
||||||
|
network: Network,
|
||||||
|
system: System,
|
||||||
|
logs: Logs,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Home = {
|
||||||
|
memory: Array<ChartEntry>,
|
||||||
|
traffic: Array<ChartEntry>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Chain = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TxPool = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Network = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type System = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Logs = {
|
||||||
|
log: Array<string>,
|
||||||
|
};
|
||||||
61
dashboard/assets/types/message.jsx
Normal file
61
dashboard/assets/types/message.jsx
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export type Message = {
|
||||||
|
home?: HomeMessage,
|
||||||
|
chain?: ChainMessage,
|
||||||
|
txpool?: TxPoolMessage,
|
||||||
|
network?: NetworkMessage,
|
||||||
|
system?: SystemMessage,
|
||||||
|
logs?: LogsMessage,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HomeMessage = {
|
||||||
|
memory?: Chart,
|
||||||
|
traffic?: Chart,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Chart = {
|
||||||
|
history?: Array<ChartEntry>,
|
||||||
|
new?: ChartEntry,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChartEntry = {
|
||||||
|
time: Date,
|
||||||
|
value: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChainMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TxPoolMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NetworkMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SystemMessage = {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LogsMessage = {
|
||||||
|
log: string,
|
||||||
|
};
|
||||||
|
|
@ -14,23 +14,61 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: './index.jsx',
|
resolve: {
|
||||||
output: {
|
extensions: ['.js', '.jsx'],
|
||||||
path: path.resolve(__dirname, 'public'),
|
},
|
||||||
filename: 'bundle.js',
|
entry: './index',
|
||||||
},
|
output: {
|
||||||
module: {
|
path: path.resolve(__dirname, 'public'),
|
||||||
loaders: [
|
filename: 'bundle.js',
|
||||||
{
|
},
|
||||||
test: /\.jsx$/, // regexp for JSX files
|
plugins: [
|
||||||
loader: 'babel-loader', // The babel configuration is in the package.json.
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
query: {
|
comments: false,
|
||||||
presets: ['env', 'react', 'stage-0']
|
mangle: false,
|
||||||
}
|
beautify: true,
|
||||||
},
|
}),
|
||||||
],
|
],
|
||||||
},
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.jsx$/, // regexp for JSX files
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: [ // order: from bottom to top
|
||||||
|
{
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: [ // order: from top to bottom
|
||||||
|
// 'transform-decorators-legacy', // @withStyles, @withTheme
|
||||||
|
'transform-class-properties', // static defaultProps
|
||||||
|
'transform-flow-strip-types',
|
||||||
|
],
|
||||||
|
presets: [ // order: from bottom to top
|
||||||
|
'env',
|
||||||
|
'react',
|
||||||
|
'stage-0',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 'eslint-loader', // show errors not only in the editor, but also in the console
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /font-awesome\.css$/,
|
||||||
|
use: [
|
||||||
|
'style-loader',
|
||||||
|
'css-loader',
|
||||||
|
path.resolve(__dirname, './fa-only-woff-loader.js'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.woff2?$/, // font-awesome icons
|
||||||
|
use: 'url-loader',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,10 @@
|
||||||
|
|
||||||
package dashboard
|
package dashboard
|
||||||
|
|
||||||
//go:generate go-bindata -nometadata -o assets.go -prefix assets -pkg dashboard assets/public/...
|
//go:generate ./assets/node_modules/.bin/webpack --config ./assets/webpack.config.js --context ./assets
|
||||||
|
//go:generate go-bindata -nometadata -o assets.go -prefix assets -nocompress -pkg dashboard assets/public/...
|
||||||
|
//go:generate gofmt -s -w assets.go
|
||||||
|
//go:generate sed -i "s#var _public#//nolint:misspell\\n&#" assets.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -40,7 +43,7 @@ const (
|
||||||
trafficSampleLimit = 200 // Maximum number of traffic data samples
|
trafficSampleLimit = 200 // Maximum number of traffic data samples
|
||||||
)
|
)
|
||||||
|
|
||||||
var nextId uint32 // Next connection id
|
var nextID uint32 // Next connection id
|
||||||
|
|
||||||
// Dashboard contains the dashboard internals.
|
// Dashboard contains the dashboard internals.
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
|
|
@ -48,46 +51,30 @@ type Dashboard struct {
|
||||||
|
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
conns map[uint32]*client // Currently live websocket connections
|
conns map[uint32]*client // Currently live websocket connections
|
||||||
charts charts // The collected data samples to plot
|
charts *HomeMessage
|
||||||
lock sync.RWMutex // Lock protecting the dashboard's internals
|
lock sync.RWMutex // Lock protecting the dashboard's internals
|
||||||
|
|
||||||
quit chan chan error // Channel used for graceful exit
|
quit chan chan error // Channel used for graceful exit
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// message embraces the data samples of a client message.
|
|
||||||
type message struct {
|
|
||||||
History *charts `json:"history,omitempty"` // Past data samples
|
|
||||||
Memory *chartEntry `json:"memory,omitempty"` // One memory sample
|
|
||||||
Traffic *chartEntry `json:"traffic,omitempty"` // One traffic sample
|
|
||||||
Log string `json:"log,omitempty"` // One log
|
|
||||||
}
|
|
||||||
|
|
||||||
// client represents active websocket connection with a remote browser.
|
// client represents active websocket connection with a remote browser.
|
||||||
type client struct {
|
type client struct {
|
||||||
conn *websocket.Conn // Particular live websocket connection
|
conn *websocket.Conn // Particular live websocket connection
|
||||||
msg chan message // Message queue for the update messages
|
msg chan Message // Message queue for the update messages
|
||||||
logger log.Logger // Logger for the particular live websocket connection
|
logger log.Logger // Logger for the particular live websocket connection
|
||||||
}
|
}
|
||||||
|
|
||||||
// charts contains the collected data samples.
|
|
||||||
type charts struct {
|
|
||||||
Memory []*chartEntry `json:"memorySamples,omitempty"`
|
|
||||||
Traffic []*chartEntry `json:"trafficSamples,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// chartEntry represents one data sample
|
|
||||||
type chartEntry struct {
|
|
||||||
Time time.Time `json:"time,omitempty"`
|
|
||||||
Value float64 `json:"value,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new dashboard instance with the given configuration.
|
// New creates a new dashboard instance with the given configuration.
|
||||||
func New(config *Config) (*Dashboard, error) {
|
func New(config *Config) (*Dashboard, error) {
|
||||||
return &Dashboard{
|
return &Dashboard{
|
||||||
conns: make(map[uint32]*client),
|
conns: make(map[uint32]*client),
|
||||||
config: config,
|
config: config,
|
||||||
quit: make(chan chan error),
|
quit: make(chan chan error),
|
||||||
|
charts: &HomeMessage{
|
||||||
|
Memory: &Chart{},
|
||||||
|
Traffic: &Chart{},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,13 +170,13 @@ func (db *Dashboard) webHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// apiHandler handles requests for the dashboard.
|
// apiHandler handles requests for the dashboard.
|
||||||
func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
id := atomic.AddUint32(&nextId, 1)
|
id := atomic.AddUint32(&nextID, 1)
|
||||||
client := &client{
|
client := &client{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
msg: make(chan message, 128),
|
msg: make(chan Message, 128),
|
||||||
logger: log.New("id", id),
|
logger: log.New("id", id),
|
||||||
}
|
}
|
||||||
done := make(chan struct{}) // Buffered channel as sender may exit early
|
done := make(chan struct{})
|
||||||
|
|
||||||
// Start listening for messages to send.
|
// Start listening for messages to send.
|
||||||
db.wg.Add(1)
|
db.wg.Add(1)
|
||||||
|
|
@ -210,8 +197,15 @@ func (db *Dashboard) apiHandler(conn *websocket.Conn) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
// Send the past data.
|
// Send the past data.
|
||||||
client.msg <- message{
|
client.msg <- Message{
|
||||||
History: &db.charts,
|
Home: &HomeMessage{
|
||||||
|
Memory: &Chart{
|
||||||
|
History: db.charts.Memory.History,
|
||||||
|
},
|
||||||
|
Traffic: &Chart{
|
||||||
|
History: db.charts.Traffic.History,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
// Start tracking the connection and drop at connection loss.
|
// Start tracking the connection and drop at connection loss.
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
|
|
@ -245,29 +239,34 @@ func (db *Dashboard) collectData() {
|
||||||
inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
inboundTraffic := metrics.DefaultRegistry.Get("p2p/InboundTraffic").(metrics.Meter).Rate1()
|
||||||
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
|
memoryInUse := metrics.DefaultRegistry.Get("system/memory/inuse").(metrics.Meter).Rate1()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
memory := &chartEntry{
|
memory := &ChartEntry{
|
||||||
Time: now,
|
Time: now,
|
||||||
Value: memoryInUse,
|
Value: memoryInUse,
|
||||||
}
|
}
|
||||||
traffic := &chartEntry{
|
traffic := &ChartEntry{
|
||||||
Time: now,
|
Time: now,
|
||||||
Value: inboundTraffic,
|
Value: inboundTraffic,
|
||||||
}
|
}
|
||||||
// Remove the first elements in case the samples' amount exceeds the limit.
|
|
||||||
first := 0
|
first := 0
|
||||||
if len(db.charts.Memory) == memorySampleLimit {
|
if len(db.charts.Memory.History) == memorySampleLimit {
|
||||||
first = 1
|
first = 1
|
||||||
}
|
}
|
||||||
db.charts.Memory = append(db.charts.Memory[first:], memory)
|
db.charts.Memory.History = append(db.charts.Memory.History[first:], memory)
|
||||||
first = 0
|
first = 0
|
||||||
if len(db.charts.Traffic) == trafficSampleLimit {
|
if len(db.charts.Traffic.History) == trafficSampleLimit {
|
||||||
first = 1
|
first = 1
|
||||||
}
|
}
|
||||||
db.charts.Traffic = append(db.charts.Traffic[first:], traffic)
|
db.charts.Traffic.History = append(db.charts.Traffic.History[first:], traffic)
|
||||||
|
|
||||||
db.sendToAll(&message{
|
db.sendToAll(&Message{
|
||||||
Memory: memory,
|
Home: &HomeMessage{
|
||||||
Traffic: traffic,
|
Memory: &Chart{
|
||||||
|
New: memory,
|
||||||
|
},
|
||||||
|
Traffic: &Chart{
|
||||||
|
New: traffic,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,6 +276,7 @@ func (db *Dashboard) collectData() {
|
||||||
func (db *Dashboard) collectLogs() {
|
func (db *Dashboard) collectLogs() {
|
||||||
defer db.wg.Done()
|
defer db.wg.Done()
|
||||||
|
|
||||||
|
id := 1
|
||||||
// TODO (kurkomisi): log collection comes here.
|
// TODO (kurkomisi): log collection comes here.
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -284,15 +284,18 @@ func (db *Dashboard) collectLogs() {
|
||||||
errc <- nil
|
errc <- nil
|
||||||
return
|
return
|
||||||
case <-time.After(db.config.Refresh / 2):
|
case <-time.After(db.config.Refresh / 2):
|
||||||
db.sendToAll(&message{
|
db.sendToAll(&Message{
|
||||||
Log: "This is a fake log.",
|
Logs: &LogsMessage{
|
||||||
|
Log: fmt.Sprintf("%-4d: This is a fake log.", id),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
id++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendToAll sends the given message to the active dashboards.
|
// sendToAll sends the given message to the active dashboards.
|
||||||
func (db *Dashboard) sendToAll(msg *message) {
|
func (db *Dashboard) sendToAll(msg *Message) {
|
||||||
db.lock.Lock()
|
db.lock.Lock()
|
||||||
for _, c := range db.conns {
|
for _, c := range db.conns {
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
63
dashboard/message.go
Normal file
63
dashboard/message.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package dashboard
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Home *HomeMessage `json:"home,omitempty"`
|
||||||
|
Chain *ChainMessage `json:"chain,omitempty"`
|
||||||
|
TxPool *TxPoolMessage `json:"txpool,omitempty"`
|
||||||
|
Network *NetworkMessage `json:"network,omitempty"`
|
||||||
|
System *SystemMessage `json:"system,omitempty"`
|
||||||
|
Logs *LogsMessage `json:"logs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HomeMessage struct {
|
||||||
|
Memory *Chart `json:"memory,omitempty"`
|
||||||
|
Traffic *Chart `json:"traffic,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chart struct {
|
||||||
|
History []*ChartEntry `json:"history,omitempty"`
|
||||||
|
New *ChartEntry `json:"new,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartEntry struct {
|
||||||
|
Time time.Time `json:"time,omitempty"`
|
||||||
|
Value float64 `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChainMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type TxPoolMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemMessage struct {
|
||||||
|
/* TODO (kurkomisi) */
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogsMessage struct {
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
}
|
||||||
251
eth/api.go
251
eth/api.go
|
|
@ -17,24 +17,19 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/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 ðapi.ExecutionResult{
|
|
||||||
Gas: gas,
|
|
||||||
Failed: failed,
|
|
||||||
ReturnValue: fmt.Sprintf("%x", ret),
|
|
||||||
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
|
||||||
}, nil
|
|
||||||
case *ethapi.JavascriptTracer:
|
|
||||||
return tracer.GetResult()
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("bad tracer type %T", tracer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// computeTxEnv returns the execution environment of a certain transaction.
|
|
||||||
func (api *PrivateDebugAPI) computeTxEnv(blockHash common.Hash, txIndex int) (core.Message, vm.Context, *state.StateDB, error) {
|
|
||||||
// Create the parent state.
|
|
||||||
block := api.eth.BlockChain().GetBlockByHash(blockHash)
|
|
||||||
if block == nil {
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("block %x not found", blockHash)
|
|
||||||
}
|
|
||||||
parent := api.eth.BlockChain().GetBlock(block.ParentHash(), block.NumberU64()-1)
|
|
||||||
if parent == nil {
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("block parent %x not found", block.ParentHash())
|
|
||||||
}
|
|
||||||
statedb, err := api.eth.BlockChain().StateAt(parent.Root())
|
|
||||||
if err != nil {
|
|
||||||
return nil, vm.Context{}, nil, err
|
|
||||||
}
|
|
||||||
txs := block.Transactions()
|
|
||||||
|
|
||||||
// Recompute transactions up to the target index.
|
|
||||||
signer := types.MakeSigner(api.config, block.Number())
|
|
||||||
for idx, tx := range txs {
|
|
||||||
// Assemble the transaction call message
|
|
||||||
msg, _ := tx.AsMessage(signer)
|
|
||||||
context := core.NewEVMContext(msg, block.Header(), api.eth.BlockChain(), nil)
|
|
||||||
if idx == txIndex {
|
|
||||||
return msg, context, statedb, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
vmenv := vm.NewEVM(context, statedb, api.config, vm.Config{})
|
|
||||||
gp := new(core.GasPool).AddGas(tx.Gas())
|
|
||||||
_, _, _, err := core.ApplyMessage(vmenv, msg, gp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err)
|
|
||||||
}
|
|
||||||
statedb.DeleteSuicides()
|
|
||||||
}
|
|
||||||
return nil, vm.Context{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
|
// Preimage is a debug API function that returns the preimage for a sha3 hash, if known.
|
||||||
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
func (api *PrivateDebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) {
|
||||||
db := core.PreimageTable(api.eth.ChainDb())
|
db := core.PreimageTable(api.eth.ChainDb())
|
||||||
|
|
@ -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
727
eth/api_tracer.go
Normal 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 ðapi.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)
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(¶ms.ChainConfig{}, genesis, db, 1, func(i int, block *core.BlockGen) {
|
blocks, _ := core.GenerateChain(¶ms.ChainConfig{}, genesis, ethash.NewFaker(), db, 1, func(i int, block *core.BlockGen) {
|
||||||
if remoteForked {
|
if remoteForked {
|
||||||
block.SetExtra(params.DAOForkBlockExtra)
|
block.SetExtra(params.DAOForkBlockExtra)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
86
eth/tracers/internal/tracers/4byte_tracer.js
Normal file
86
eth/tracers/internal/tracers/4byte_tracer.js
Normal 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;
|
||||||
|
},
|
||||||
|
}
|
||||||
350
eth/tracers/internal/tracers/assets.go
Normal file
350
eth/tracers/internal/tracers/assets.go
Normal file
File diff suppressed because one or more lines are too long
246
eth/tracers/internal/tracers/call_tracer.js
Normal file
246
eth/tracers/internal/tracers/call_tracer.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
93
eth/tracers/internal/tracers/evmdis_tracer.js
Normal file
93
eth/tracers/internal/tracers/evmdis_tracer.js
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
eth/tracers/internal/tracers/noop_tracer.js
Normal file
29
eth/tracers/internal/tracers/noop_tracer.js
Normal 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) { }
|
||||||
|
}
|
||||||
32
eth/tracers/internal/tracers/opcount_tracer.js
Normal file
32
eth/tracers/internal/tracers/opcount_tracer.js
Normal 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 }
|
||||||
|
}
|
||||||
103
eth/tracers/internal/tracers/prestate_tracer.js
Normal file
103
eth/tracers/internal/tracers/prestate_tracer.js
Normal 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) {}
|
||||||
|
}
|
||||||
21
eth/tracers/internal/tracers/tracers.go
Normal file
21
eth/tracers/internal/tracers/tracers.go
Normal 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
|
||||||
58
eth/tracers/testdata/call_tracer_create.json
vendored
Normal file
58
eth/tracers/testdata/call_tracer_create.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
415
eth/tracers/testdata/call_tracer_deep_calls.json
vendored
Normal file
415
eth/tracers/testdata/call_tracer_deep_calls.json
vendored
Normal file
File diff suppressed because one or more lines are too long
97
eth/tracers/testdata/call_tracer_delegatecall.json
vendored
Normal file
97
eth/tracers/testdata/call_tracer_delegatecall.json
vendored
Normal file
File diff suppressed because one or more lines are too long
77
eth/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json
vendored
Normal file
77
eth/tracers/testdata/call_tracer_inner_create_oog_outer_throw.json
vendored
Normal file
File diff suppressed because one or more lines are too long
81
eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json
vendored
Normal file
81
eth/tracers/testdata/call_tracer_inner_throw_outer_revert.json
vendored
Normal file
File diff suppressed because one or more lines are too long
60
eth/tracers/testdata/call_tracer_oog.json
vendored
Normal file
60
eth/tracers/testdata/call_tracer_oog.json
vendored
Normal file
File diff suppressed because one or more lines are too long
58
eth/tracers/testdata/call_tracer_revert.json
vendored
Normal file
58
eth/tracers/testdata/call_tracer_revert.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
78
eth/tracers/testdata/call_tracer_simple.json
vendored
Normal file
78
eth/tracers/testdata/call_tracer_simple.json
vendored
Normal 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
Loading…
Reference in a new issue