Merge branch 'develop' into conversion

This commit is contained in:
obscuren 2015-03-17 21:40:05 +01:00
commit 142e81258c
29 changed files with 1198 additions and 506 deletions

259
cmd/ethereum/admin.go Normal file
View File

@ -0,0 +1,259 @@
package main
import (
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/xeth"
"github.com/obscuren/otto"
)
/*
node admin bindings
*/
func (js *jsre) adminBindings() {
js.re.Set("admin", struct{}{})
t, _ := js.re.Get("admin")
admin := t.Object()
admin.Set("suggestPeer", js.suggestPeer)
admin.Set("startRPC", js.startRPC)
admin.Set("startMining", js.startMining)
admin.Set("stopMining", js.stopMining)
admin.Set("nodeInfo", js.nodeInfo)
admin.Set("peers", js.peers)
admin.Set("newAccount", js.newAccount)
admin.Set("unlock", js.unlock)
admin.Set("import", js.importChain)
admin.Set("export", js.exportChain)
admin.Set("dumpBlock", js.dumpBlock)
}
func (js *jsre) startMining(call otto.FunctionCall) otto.Value {
_, err := call.Argument(0).ToInteger()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
// threads now ignored
err = js.ethereum.StartMining()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
return otto.TrueValue()
}
func (js *jsre) stopMining(call otto.FunctionCall) otto.Value {
js.ethereum.StopMining()
return otto.TrueValue()
}
func (js *jsre) startRPC(call otto.FunctionCall) otto.Value {
addr, err := call.Argument(0).ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
port, err := call.Argument(1).ToInteger()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
dataDir := js.ethereum.DataDir
l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addr, port))
if err != nil {
fmt.Printf("Can't listen on %s:%d: %v", addr, port, err)
return otto.FalseValue()
}
go http.Serve(l, rpc.JSONRPC(xeth.New(js.ethereum, nil), dataDir))
return otto.TrueValue()
}
func (js *jsre) suggestPeer(call otto.FunctionCall) otto.Value {
nodeURL, err := call.Argument(0).ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
err = js.ethereum.SuggestPeer(nodeURL)
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
return otto.TrueValue()
}
func (js *jsre) unlock(call otto.FunctionCall) otto.Value {
addr, err := call.Argument(0).ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
seconds, err := call.Argument(2).ToInteger()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
arg := call.Argument(1)
var passphrase string
if arg.IsUndefined() {
fmt.Println("Please enter a passphrase now.")
passphrase, err = readPassword("Passphrase: ", true)
if err != nil {
utils.Fatalf("%v", err)
}
} else {
passphrase, err = arg.ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
}
am := js.ethereum.AccountManager()
// err := am.Unlock(common.FromHex(split[0]), split[1])
// if err != nil {
// utils.Fatalf("Unlock account failed '%v'", err)
// }
err = am.TimedUnlock(common.FromHex(addr), passphrase, time.Duration(seconds)*time.Second)
if err != nil {
fmt.Printf("Unlock account failed '%v'\n", err)
return otto.FalseValue()
}
return otto.TrueValue()
}
func (js *jsre) newAccount(call otto.FunctionCall) otto.Value {
arg := call.Argument(0)
var passphrase string
if arg.IsUndefined() {
fmt.Println("The new account will be encrypted with a passphrase.")
fmt.Println("Please enter a passphrase now.")
auth, err := readPassword("Passphrase: ", true)
if err != nil {
utils.Fatalf("%v", err)
}
confirm, err := readPassword("Repeat Passphrase: ", false)
if err != nil {
utils.Fatalf("%v", err)
}
if auth != confirm {
utils.Fatalf("Passphrases did not match.")
}
passphrase = auth
} else {
var err error
passphrase, err = arg.ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
}
acct, err := js.ethereum.AccountManager().NewAccount(passphrase)
if err != nil {
fmt.Printf("Could not create the account: %v", err)
return otto.UndefinedValue()
}
return js.re.ToVal(common.Bytes2Hex(acct.Address))
}
func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {
return js.re.ToVal(js.ethereum.NodeInfo())
}
func (js *jsre) peers(call otto.FunctionCall) otto.Value {
return js.re.ToVal(js.ethereum.PeersInfo())
}
func (js *jsre) importChain(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) == 0 {
fmt.Println("err: require file name")
return otto.FalseValue()
}
fn, err := call.Argument(0).ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
var fh *os.File
fh, err = os.OpenFile(fn, os.O_RDONLY, os.ModePerm)
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
defer fh.Close()
var blocks types.Blocks
if err = rlp.Decode(fh, &blocks); err != nil {
fmt.Println(err)
return otto.FalseValue()
}
js.ethereum.ChainManager().Reset()
if err = js.ethereum.ChainManager().InsertChain(blocks); err != nil {
fmt.Println(err)
return otto.FalseValue()
}
return otto.TrueValue()
}
func (js *jsre) exportChain(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) == 0 {
fmt.Println("err: require file name")
return otto.FalseValue()
}
fn, err := call.Argument(0).ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
data := js.ethereum.ChainManager().Export()
if err := common.WriteFile(fn, data); err != nil {
fmt.Println(err)
return otto.FalseValue()
}
return otto.TrueValue()
}
func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value {
var block *types.Block
if len(call.ArgumentList) > 0 {
if call.Argument(0).IsNumber() {
num, _ := call.Argument(0).ToInteger()
block = js.ethereum.ChainManager().GetBlockByNumber(uint64(num))
} else if call.Argument(0).IsString() {
hash, _ := call.Argument(0).ToString()
block = js.ethereum.ChainManager().GetBlock(common.Hex2Bytes(hash))
} else {
fmt.Println("invalid argument for dump. Either hex string or number")
}
} else {
block = js.ethereum.ChainManager().CurrentBlock()
}
if block == nil {
fmt.Println("block not found")
return otto.UndefinedValue()
}
statedb := state.New(block.Root(), js.ethereum.StateDb())
dump := statedb.RawDump()
return js.re.ToVal(dump)
}

View File

@ -20,18 +20,16 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path" "path"
"strings" "strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/common" re "github.com/ethereum/go-ethereum/jsre"
"github.com/ethereum/go-ethereum/javascript" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
"github.com/obscuren/otto"
"github.com/peterh/liner" "github.com/peterh/liner"
) )
@ -59,7 +57,7 @@ func (r dumbterm) PasswordPrompt(p string) (string, error) {
func (r dumbterm) AppendHistory(string) {} func (r dumbterm) AppendHistory(string) {}
type jsre struct { type jsre struct {
re *javascript.JSRE re *re.JSRE
ethereum *eth.Ethereum ethereum *eth.Ethereum
xeth *xeth.XEth xeth *xeth.XEth
ps1 string ps1 string
@ -68,11 +66,12 @@ type jsre struct {
prompter prompter
} }
func newJSRE(ethereum *eth.Ethereum) *jsre { func newJSRE(ethereum *eth.Ethereum, libPath string) *jsre {
js := &jsre{ethereum: ethereum, ps1: "> "} js := &jsre{ethereum: ethereum, ps1: "> "}
js.xeth = xeth.New(ethereum, js) js.xeth = xeth.New(ethereum, js)
js.re = javascript.NewJSRE(js.xeth) js.re = re.New(libPath)
js.initStdFuncs() js.apiBindings()
js.adminBindings()
if !liner.TerminalSupported() { if !liner.TerminalSupported() {
js.prompter = dumbterm{bufio.NewReader(os.Stdin)} js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
@ -89,6 +88,49 @@ func newJSRE(ethereum *eth.Ethereum) *jsre {
return js return js
} }
func (js *jsre) apiBindings() {
ethApi := rpc.NewEthereumApi(js.xeth, js.ethereum.DataDir)
js.re.Bind("jeth", rpc.NewJeth(ethApi, js.re.ToVal))
_, err := js.re.Eval(re.BigNumber_JS)
if err != nil {
utils.Fatalf("Error loading bignumber.js: %v", err)
}
// we need to declare a dummy setTimeout. Otto does not support it
_, err = js.re.Eval("setTimeout = function(cb, delay) {};")
if err != nil {
utils.Fatalf("Error defining setTimeout: %v", err)
}
_, err = js.re.Eval(re.Ethereum_JS)
if err != nil {
utils.Fatalf("Error loading ethereum.js: %v", err)
}
_, err = js.re.Eval("var web3 = require('web3');")
if err != nil {
utils.Fatalf("Error requiring web3: %v", err)
}
_, err = js.re.Eval("web3.setProvider(jeth)")
if err != nil {
utils.Fatalf("Error setting web3 provider: %v", err)
}
_, err = js.re.Eval(`
var eth = web3.eth;
var shh = web3.shh;
var db = web3.db;
var net = web3.net;
`)
if err != nil {
utils.Fatalf("Error setting namespaces: %v", err)
}
}
func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool { func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool {
p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx) p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx)
answer, _ := self.Prompt(p) answer, _ := self.Prompt(p)
@ -111,15 +153,7 @@ func (self *jsre) UnlockAccount(addr []byte) bool {
} }
func (self *jsre) exec(filename string) error { func (self *jsre) exec(filename string) error {
file, err := os.Open(filename) if err := self.re.Exec(filename); err != nil {
if err != nil {
return err
}
content, err := ioutil.ReadAll(file)
if err != nil {
return err
}
if _, err := self.re.Run(string(content)); err != nil {
return fmt.Errorf("Javascript Error: %v", err) return fmt.Errorf("Javascript Error: %v", err)
} }
return nil return nil
@ -193,102 +227,8 @@ func (self *jsre) setIndent() {
} }
func (self *jsre) printValue(v interface{}) { func (self *jsre) printValue(v interface{}) {
method, _ := self.re.Vm.Get("prettyPrint") val, err := self.re.PrettyPrint(v)
v, err := self.re.Vm.ToValue(v)
if err == nil {
val, err := method.Call(method, v)
if err == nil { if err == nil {
fmt.Printf("%v", val) fmt.Printf("%v", val)
} }
} }
}
func (self *jsre) initStdFuncs() {
t, _ := self.re.Vm.Get("eth")
eth := t.Object()
eth.Set("connect", self.connect)
eth.Set("stopMining", self.stopMining)
eth.Set("startMining", self.startMining)
eth.Set("dump", self.dump)
eth.Set("export", self.export)
}
/*
* The following methods are natively implemented javascript functions.
*/
func (self *jsre) dump(call otto.FunctionCall) otto.Value {
var block *types.Block
if len(call.ArgumentList) > 0 {
if call.Argument(0).IsNumber() {
num, _ := call.Argument(0).ToInteger()
block = self.ethereum.ChainManager().GetBlockByNumber(uint64(num))
} else if call.Argument(0).IsString() {
hash, _ := call.Argument(0).ToString()
block = self.ethereum.ChainManager().GetBlock(common.Hex2Bytes(hash))
} else {
fmt.Println("invalid argument for dump. Either hex string or number")
}
if block == nil {
fmt.Println("block not found")
return otto.UndefinedValue()
}
} else {
block = self.ethereum.ChainManager().CurrentBlock()
}
statedb := state.New(block.Root(), self.ethereum.StateDb())
v, _ := self.re.Vm.ToValue(statedb.RawDump())
return v
}
func (self *jsre) stopMining(call otto.FunctionCall) otto.Value {
self.ethereum.StopMining()
return otto.TrueValue()
}
func (self *jsre) startMining(call otto.FunctionCall) otto.Value {
if err := self.ethereum.StartMining(); err != nil {
return otto.FalseValue()
}
return otto.TrueValue()
}
func (self *jsre) connect(call otto.FunctionCall) otto.Value {
nodeURL, err := call.Argument(0).ToString()
if err != nil {
return otto.FalseValue()
}
if err := self.ethereum.SuggestPeer(nodeURL); err != nil {
return otto.FalseValue()
}
return otto.TrueValue()
}
func (self *jsre) export(call otto.FunctionCall) otto.Value {
if len(call.ArgumentList) == 0 {
fmt.Println("err: require file name")
return otto.FalseValue()
}
fn, err := call.Argument(0).ToString()
if err != nil {
fmt.Println(err)
return otto.FalseValue()
}
data := self.ethereum.ChainManager().Export()
if err := common.WriteFile(fn, data); err != nil {
fmt.Println(err)
return otto.FalseValue()
}
return otto.TrueValue()
}

252
cmd/ethereum/js_test.go Normal file
View File

@ -0,0 +1,252 @@
package main
import (
"fmt"
"github.com/obscuren/otto"
"os"
"path"
"testing"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
)
var port = 30300
func testJEthRE(t *testing.T) (repl *jsre, ethereum *eth.Ethereum, err error) {
os.RemoveAll("/tmp/eth/")
err = os.MkdirAll("/tmp/eth/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/", os.ModePerm)
if err != nil {
t.Errorf("%v", err)
return
}
err = os.MkdirAll("/tmp/eth/data", os.ModePerm)
if err != nil {
t.Errorf("%v", err)
return
}
// FIXME: this does not work ATM
ks := crypto.NewKeyStorePlain("/tmp/eth/keys")
common.WriteFile("/tmp/eth/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/e273f01c99144c438695e10f24926dc1f9fbf62d",
[]byte(`{"Id":"RhRXD+fNRKS4jx+7ZfEsNA==","Address":"4nPwHJkUTEOGleEPJJJtwfn79i0=","PrivateKey":"h4ACVpe74uIvi5Cg/2tX/Yrm2xdr3J7QoMbMtNX2CNc="}`))
port++
ethereum, err = eth.New(&eth.Config{
DataDir: "/tmp/eth",
AccountManager: accounts.NewManager(ks),
Port: fmt.Sprintf("%d", port),
MaxPeers: 10,
Name: "test",
})
if err != nil {
t.Errorf("%v", err)
return
}
assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
repl = newJSRE(ethereum, assetPath)
return
}
func TestNodeInfo(t *testing.T) {
repl, ethereum, err := testJEthRE(t)
if err != nil {
t.Errorf("error creating jsre, got %v", err)
return
}
err = ethereum.Start()
if err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
val, err := repl.re.Run("admin.nodeInfo()")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
exp, err := val.Export()
if err != nil {
t.Errorf("expected no error, got %v", err)
}
nodeInfo, ok := exp.(*eth.NodeInfo)
if !ok {
t.Errorf("expected nodeInfo, got %v", err)
}
exp = "test"
got := nodeInfo.Name
if exp != got {
t.Errorf("expected %v, got %v", exp, got)
}
exp = 30301
port := nodeInfo.DiscPort
if exp != port {
t.Errorf("expected %v, got %v", exp, port)
}
exp = 30301
port = nodeInfo.TCPPort
if exp != port {
t.Errorf("expected %v, got %v", exp, port)
}
}
func TestAccounts(t *testing.T) {
repl, ethereum, err := testJEthRE(t)
if err != nil {
t.Errorf("error creating jsre, got %v", err)
return
}
err = ethereum.Start()
if err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
val, err := repl.re.Run("eth.coinbase")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
pp, err := repl.re.PrettyPrint(val)
if err != nil {
t.Errorf("%v", err)
}
if !val.IsString() {
t.Errorf("incorrect type, expected string, got %v: %v", val, pp)
}
strVal, _ := val.ToString()
expected := "0xe273f01c99144c438695e10f24926dc1f9fbf62d"
if strVal != expected {
t.Errorf("incorrect result, expected %s, got %v", expected, strVal)
}
val, err = repl.re.Run(`admin.newAccount("password")`)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
addr, err := val.ToString()
if err != nil {
t.Errorf("expected string, got %v", err)
}
val, err = repl.re.Run("eth.accounts")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
exp, err := val.Export()
if err != nil {
t.Errorf("expected no error, got %v", err)
}
addrs, ok := exp.([]string)
if !ok {
t.Errorf("expected []string, got %v", err)
}
if len(addrs) != 2 || (addr != addrs[0][2:] && addr != addrs[1][2:]) {
t.Errorf("expected addrs == [<default>, <new>], got %v (%v)", addrs, addr)
}
}
func TestBlockChain(t *testing.T) {
repl, ethereum, err := testJEthRE(t)
if err != nil {
t.Errorf("error creating jsre, got %v", err)
return
}
err = ethereum.Start()
if err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
// should get current block
val0, err := repl.re.Run("admin.dumpBlock()")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
fn := "/tmp/eth/data/blockchain.0"
_, err = repl.re.Run("admin.export(\"" + fn + "\")")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
if _, err = os.Stat(fn); err != nil {
t.Errorf("expected no error on file, got %v", err)
}
_, err = repl.re.Run("admin.import(\"" + fn + "\")")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
var val1 otto.Value
// should get current block
val1, err = repl.re.Run("admin.dumpBlock()")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
// FIXME: neither != , nor reflect.DeepEqual works, doing string comparison
v0 := fmt.Sprintf("%v", val0)
v1 := fmt.Sprintf("%v", val1)
if v0 != v1 {
t.Errorf("expected same head after export-import, got %v (!=%v)", v1, v0)
}
}
func TestMining(t *testing.T) {
repl, ethereum, err := testJEthRE(t)
if err != nil {
t.Errorf("error creating jsre, got %v", err)
return
}
err = ethereum.Start()
if err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
val, err := repl.re.Run("eth.mining")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
var mining bool
mining, err = val.ToBoolean()
if err != nil {
t.Errorf("expected boolean, got %v", err)
}
if mining {
t.Errorf("expected false (not mining), got true")
}
}
func TestRPC(t *testing.T) {
repl, ethereum, err := testJEthRE(t)
if err != nil {
t.Errorf("error creating jsre, got %v", err)
return
}
err = ethereum.Start()
if err != nil {
t.Errorf("error starting ethereum: %v", err)
return
}
defer ethereum.Stop()
val, err := repl.re.Run(`admin.startRPC("127.0.0.1", 5004)`)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
success, _ := val.ToBoolean()
if !success {
t.Errorf("expected true (started), got false")
}
}

View File

@ -31,9 +31,9 @@ import (
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
"github.com/peterh/liner" "github.com/peterh/liner"
@ -41,7 +41,7 @@ import (
const ( const (
ClientIdentifier = "Ethereum(G)" ClientIdentifier = "Ethereum(G)"
Version = "0.9.0" Version = "0.9.1"
) )
var ( var (
@ -89,16 +89,20 @@ Use "ethereum dump 0" to dump the genesis block.
`, `,
}, },
{ {
Action: runjs, Action: console,
Name: "js", Name: "console",
Usage: `interactive JavaScript console`, Usage: `Ethereum Console: interactive JavaScript environment`,
Description: ` Description: `
In the console, you can use the eth object to interact Console is an interactive shell for the Ethereum JavaScript runtime environment which exposes a node admin interface as well as the DAPP JavaScript API.
with the running ethereum stack. The API does not match See https://github.com/ethereum/go-ethereum/wiki/Frontier-Console
ethereum.js. `,
},
A JavaScript file can be provided as the argument. The {
runtime will execute the file and exit. Action: execJSFiles,
Name: "js",
Usage: `executes the given JavaScript files in the Ethereum Frontier JavaScript VM`,
Description: `
The Ethereum JavaScript VM exposes a node admin interface as well as the DAPP JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Frontier-Console
`, `,
}, },
{ {
@ -116,6 +120,7 @@ runtime will execute the file and exit.
utils.UnlockedAccountFlag, utils.UnlockedAccountFlag,
utils.BootnodesFlag, utils.BootnodesFlag,
utils.DataDirFlag, utils.DataDirFlag,
utils.JSpathFlag,
utils.ListenPortFlag, utils.ListenPortFlag,
utils.LogFileFlag, utils.LogFileFlag,
utils.LogFormatFlag, utils.LogFormatFlag,
@ -131,6 +136,7 @@ runtime will execute the file and exit.
utils.RPCPortFlag, utils.RPCPortFlag,
utils.UnencryptedKeysFlag, utils.UnencryptedKeysFlag,
utils.VMDebugFlag, utils.VMDebugFlag,
//utils.VMTypeFlag, //utils.VMTypeFlag,
} }
@ -168,7 +174,7 @@ func run(ctx *cli.Context) {
ethereum.WaitForShutdown() ethereum.WaitForShutdown()
} }
func runjs(ctx *cli.Context) { func console(ctx *cli.Context) {
cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
ethereum, err := eth.New(cfg) ethereum, err := eth.New(cfg)
if err != nil { if err != nil {
@ -176,14 +182,26 @@ func runjs(ctx *cli.Context) {
} }
startEth(ctx, ethereum) startEth(ctx, ethereum)
repl := newJSRE(ethereum) repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name))
if len(ctx.Args()) == 0 {
repl.interactive() repl.interactive()
} else {
ethereum.Stop()
ethereum.WaitForShutdown()
}
func execJSFiles(ctx *cli.Context) {
cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx)
ethereum, err := eth.New(cfg)
if err != nil {
utils.Fatalf("%v", err)
}
startEth(ctx, ethereum)
repl := newJSRE(ethereum, ctx.String(utils.JSpathFlag.Name))
for _, file := range ctx.Args() { for _, file := range ctx.Args() {
repl.exec(file) repl.exec(file)
} }
}
ethereum.Stop() ethereum.Stop()
ethereum.WaitForShutdown() ethereum.WaitForShutdown()
} }

File diff suppressed because one or more lines are too long

View File

@ -157,7 +157,6 @@ ApplicationWindow {
} }
menuBar: MenuBar { menuBar: MenuBar {
Menu { Menu {
title: "File" title: "File"
@ -206,15 +205,6 @@ ApplicationWindow {
} }
} }
MenuItem {
text: "Run JS file"
onTriggered: {
generalFileDialog.show(true, function(path) {
eth.evalJavascriptFile(path)
})
}
}
MenuItem { MenuItem {
text: "Dump state" text: "Dump state"
onTriggered: { onTriggered: {

View File

@ -25,9 +25,7 @@ import "C"
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"math/big" "math/big"
"os"
"path" "path"
"runtime" "runtime"
"sort" "sort"
@ -99,7 +97,7 @@ func NewWindow(ethereum *eth.Ethereum) *Gui {
return gui return gui
} }
func (gui *Gui) Start(assetPath string) { func (gui *Gui) Start(assetPath, libPath string) {
defer gui.txDb.Close() defer gui.txDb.Close()
guilogger.Infoln("Starting GUI") guilogger.Infoln("Starting GUI")
@ -117,7 +115,7 @@ func (gui *Gui) Start(assetPath string) {
// Create a new QML engine // Create a new QML engine
gui.engine = qml.NewEngine() gui.engine = qml.NewEngine()
context := gui.engine.Context() context := gui.engine.Context()
gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath) gui.uiLib = NewUiLib(gui.engine, gui.eth, assetPath, libPath)
gui.whisper = qwhisper.New(gui.eth.Whisper()) gui.whisper = qwhisper.New(gui.eth.Whisper())
// Expose the eth library and the ui library to QML // Expose the eth library and the ui library to QML
@ -292,25 +290,6 @@ func (self *Gui) getObjectByName(objectName string) qml.Object {
return self.win.Root().ObjectByName(objectName) return self.win.Root().ObjectByName(objectName)
} }
func loadJavascriptAssets(gui *Gui) (jsfiles string) {
for _, fn := range []string{"ext/q.js", "ext/eth.js/main.js", "ext/eth.js/qt.js", "ext/setup.js"} {
f, err := os.Open(gui.uiLib.AssetPath(fn))
if err != nil {
fmt.Println(err)
continue
}
content, err := ioutil.ReadAll(f)
if err != nil {
fmt.Println(err)
continue
}
jsfiles += string(content)
}
return
}
func (gui *Gui) SendCommand(cmd ServEv) { func (gui *Gui) SendCommand(cmd ServEv) {
gui.serviceEvents <- cmd gui.serviceEvents <- cmd
} }

View File

@ -65,6 +65,7 @@ func init() {
utils.NodeKeyFileFlag, utils.NodeKeyFileFlag,
utils.RPCListenAddrFlag, utils.RPCListenAddrFlag,
utils.RPCPortFlag, utils.RPCPortFlag,
utils.JSpathFlag,
} }
} }
@ -111,7 +112,7 @@ func run(ctx *cli.Context) {
gui := NewWindow(ethereum) gui := NewWindow(ethereum)
utils.RegisterInterrupt(func(os.Signal) { gui.Stop() }) utils.RegisterInterrupt(func(os.Signal) { gui.Stop() })
// gui blocks the main thread // gui blocks the main thread
gui.Start(ctx.GlobalString(assetPathFlag.Name)) gui.Start(ctx.GlobalString(assetPathFlag.Name), ctx.GlobalString(utils.JSpathFlag.Name))
return nil return nil
}) })
} }

View File

@ -21,7 +21,6 @@
package main package main
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"path" "path"
@ -29,7 +28,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/event/filter" "github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/javascript"
"github.com/ethereum/go-ethereum/xeth" "github.com/ethereum/go-ethereum/xeth"
"github.com/obscuren/qml" "github.com/obscuren/qml"
) )
@ -49,15 +47,19 @@ type UiLib struct {
// The main application window // The main application window
win *qml.Window win *qml.Window
jsEngine *javascript.JSRE
filterCallbacks map[int][]int filterCallbacks map[int][]int
filterManager *filter.FilterManager filterManager *filter.FilterManager
} }
func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath, libPath string) *UiLib {
x := xeth.New(eth, nil) x := xeth.New(eth, nil)
lib := &UiLib{XEth: x, engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(x), filterCallbacks: make(map[int][]int)} //, filters: make(map[int]*xeth.JSFilter)} lib := &UiLib{
XEth: x,
engine: engine,
eth: eth,
assetPath: assetPath,
filterCallbacks: make(map[int][]int),
}
lib.filterManager = filter.NewFilterManager(eth.EventMux()) lib.filterManager = filter.NewFilterManager(eth.EventMux())
go lib.filterManager.Start() go lib.filterManager.Start()
@ -76,19 +78,6 @@ func (self *UiLib) ImportTx(rlpTx string) {
} }
} }
func (self *UiLib) EvalJavascriptFile(path string) {
self.jsEngine.LoadExtFile(path[7:])
}
func (self *UiLib) EvalJavascriptString(str string) string {
value, err := self.jsEngine.Run(str)
if err != nil {
return err.Error()
}
return fmt.Sprintf("%v", value)
}
func (ui *UiLib) Muted(content string) { func (ui *UiLib) Muted(content string) {
component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml")) component, err := ui.engine.LoadFile(ui.AssetPath("qml/muted.qml"))
if err != nil { if err != nil {

View File

@ -163,6 +163,11 @@ var (
Usage: "Port mapping mechanism (any|none|upnp|pmp|extip:<IP>)", Usage: "Port mapping mechanism (any|none|upnp|pmp|extip:<IP>)",
Value: "any", Value: "any",
} }
JSpathFlag = cli.StringFlag{
Name: "jspath",
Usage: "JS library path to be used with console and js subcommands",
Value: ".",
}
) )
func GetNAT(ctx *cli.Context) nat.Interface { func GetNAT(ctx *cli.Context) nat.Interface {

View File

@ -219,6 +219,64 @@ func New(config *Config) (*Ethereum, error) {
return eth, nil return eth, nil
} }
type NodeInfo struct {
Name string
NodeUrl string
NodeID string
IP string
DiscPort int // UDP listening port for discovery protocol
TCPPort int // TCP listening port for RLPx
Td string
ListenAddr string
}
func (s *Ethereum) NodeInfo() *NodeInfo {
node := s.net.Self()
return &NodeInfo{
Name: s.Name(),
NodeUrl: node.String(),
NodeID: node.ID.String(),
IP: node.IP.String(),
DiscPort: node.DiscPort,
TCPPort: node.TCPPort,
ListenAddr: s.net.ListenAddr,
Td: s.ChainManager().Td().String(),
}
}
type PeerInfo struct {
ID string
Name string
Caps string
RemoteAddress string
LocalAddress string
}
func newPeerInfo(peer *p2p.Peer) *PeerInfo {
var caps []string
for _, cap := range peer.Caps() {
caps = append(caps, cap.String())
}
return &PeerInfo{
ID: peer.ID().String(),
Name: peer.Name(),
Caps: strings.Join(caps, ", "),
RemoteAddress: peer.RemoteAddr().String(),
LocalAddress: peer.LocalAddr().String(),
}
}
// PeersInfo returns an array of PeerInfo objects describing connected peers
func (s *Ethereum) PeersInfo() (peersinfo []*PeerInfo) {
for _, peer := range s.net.Peers() {
if peer != nil {
peersinfo = append(peersinfo, newPeerInfo(peer))
}
}
return
}
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) { func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
s.chainManager.ResetWithGenesisBlock(gb) s.chainManager.ResetWithGenesisBlock(gb)
s.pow.UpdateCache(true) s.pow.UpdateCache(true)
@ -251,6 +309,7 @@ func (s *Ethereum) StateDb() common.Database { return s.stateDb }
func (s *Ethereum) ExtraDb() common.Database { return s.extraDb } func (s *Ethereum) ExtraDb() common.Database { return s.extraDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) IsListening() bool { return true } // Always listening
func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } func (s *Ethereum) PeerCount() int { return s.net.PeerCount() }
func (s *Ethereum) PeerInfo() int { return s.net.PeerCount() }
func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() }
func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers } func (s *Ethereum) MaxPeers() int { return s.net.MaxPeers }
func (s *Ethereum) Version() string { return s.version } func (s *Ethereum) Version() string { return s.version }
@ -262,10 +321,12 @@ func (s *Ethereum) Start() error {
ProtocolVersion: ProtocolVersion, ProtocolVersion: ProtocolVersion,
}) })
if s.net.MaxPeers > 0 {
err := s.net.Start() err := s.net.Start()
if err != nil { if err != nil {
return err return err
} }
}
// Start services // Start services
s.txPool.Start() s.txPool.Start()
@ -311,6 +372,7 @@ func (s *Ethereum) Stop() {
// Close the database // Close the database
defer s.blockDb.Close() defer s.blockDb.Close()
defer s.stateDb.Close() defer s.stateDb.Close()
defer s.extraDb.Close()
s.txSub.Unsubscribe() // quits txBroadcastLoop s.txSub.Unsubscribe() // quits txBroadcastLoop
s.blockSub.Unsubscribe() // quits blockBroadcastLoop s.blockSub.Unsubscribe() // quits blockBroadcastLoop

View File

@ -1,103 +0,0 @@
package javascript
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/xeth"
"github.com/obscuren/otto"
)
var jsrelogger = logger.NewLogger("JSRE")
type JSRE struct {
Vm *otto.Otto
xeth *xeth.XEth
objectCb map[string][]otto.Value
}
func (jsre *JSRE) LoadExtFile(path string) {
result, err := ioutil.ReadFile(path)
if err == nil {
jsre.Vm.Run(result)
} else {
jsrelogger.Infoln("Could not load file:", path)
}
}
func (jsre *JSRE) LoadIntFile(file string) {
assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
jsre.LoadExtFile(path.Join(assetPath, file))
}
func NewJSRE(xeth *xeth.XEth) *JSRE {
re := &JSRE{
otto.New(),
xeth,
make(map[string][]otto.Value),
}
// Init the JS lib
re.Vm.Run(jsLib)
// Load extra javascript files
re.LoadIntFile("bignumber.min.js")
re.Bind("eth", &JSEthereum{re.xeth, re.Vm})
re.initStdFuncs()
jsrelogger.Infoln("started")
return re
}
func (self *JSRE) Bind(name string, v interface{}) {
self.Vm.Set(name, v)
}
func (self *JSRE) Run(code string) (otto.Value, error) {
return self.Vm.Run(code)
}
func (self *JSRE) initStdFuncs() {
t, _ := self.Vm.Get("eth")
eth := t.Object()
eth.Set("require", self.require)
}
func (self *JSRE) Require(file string) error {
if len(filepath.Ext(file)) == 0 {
file += ".js"
}
fh, err := os.Open(file)
if err != nil {
return err
}
content, _ := ioutil.ReadAll(fh)
self.Run("exports = {};(function() {" + string(content) + "})();")
return nil
}
func (self *JSRE) require(call otto.FunctionCall) otto.Value {
file, err := call.Argument(0).ToString()
if err != nil {
return otto.UndefinedValue()
}
if err := self.Require(file); err != nil {
fmt.Println("err:", err)
return otto.UndefinedValue()
}
t, _ := self.Vm.Get("exports")
return t
}

View File

@ -1,94 +0,0 @@
package javascript
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/state"
"github.com/ethereum/go-ethereum/xeth"
"github.com/obscuren/otto"
)
type JSStateObject struct {
*xeth.Object
eth *JSEthereum
}
func (self *JSStateObject) EachStorage(call otto.FunctionCall) otto.Value {
cb := call.Argument(0)
it := self.Object.Trie().Iterator()
for it.Next() {
cb.Call(self.eth.toVal(self), self.eth.toVal(common.Bytes2Hex(it.Key)), self.eth.toVal(common.Bytes2Hex(it.Value)))
}
return otto.UndefinedValue()
}
// The JSEthereum object attempts to wrap the PEthereum object and returns
// meaningful javascript objects
type JSBlock struct {
*xeth.Block
eth *JSEthereum
}
func (self *JSBlock) GetTransaction(hash string) otto.Value {
return self.eth.toVal(self.Block.GetTransaction(hash))
}
type JSLog struct {
Address string `json:address`
Topics []string `json:topics`
Number int32 `json:number`
Data string `json:data`
}
func NewJSLog(log state.Log) JSLog {
return JSLog{
Address: common.Bytes2Hex(log.Address()),
Topics: nil, //common.Bytes2Hex(log.Address()),
Number: 0,
Data: common.Bytes2Hex(log.Data()),
}
}
type JSEthereum struct {
*xeth.XEth
vm *otto.Otto
}
func (self *JSEthereum) Block(v interface{}) otto.Value {
if number, ok := v.(int64); ok {
return self.toVal(&JSBlock{self.XEth.BlockByNumber(number), self})
} else if hash, ok := v.(string); ok {
return self.toVal(&JSBlock{self.XEth.BlockByHash(hash), self})
}
return otto.UndefinedValue()
}
func (self *JSEthereum) GetStateObject(addr string) otto.Value {
return self.toVal(&JSStateObject{self.XEth.State().SafeGet(addr), self})
}
func (self *JSEthereum) Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value {
r, err := self.XEth.Transact(fromStr, recipient, valueStr, gasStr, gasPriceStr, dataStr)
if err != nil {
fmt.Println(err)
return otto.UndefinedValue()
}
return self.toVal(r)
}
func (self *JSEthereum) toVal(v interface{}) otto.Value {
result, err := self.vm.ToValue(v)
if err != nil {
fmt.Println("Value unknown:", err)
return otto.UndefinedValue()
}
return result
}

6
jsre/bignumber_js.go Normal file

File diff suppressed because one or more lines are too long

3
jsre/ethereum_js.go Normal file

File diff suppressed because one or more lines are too long

115
jsre/jsre.go Normal file
View File

@ -0,0 +1,115 @@
package jsre
import (
"fmt"
"github.com/obscuren/otto"
"io/ioutil"
"github.com/ethereum/go-ethereum/common"
)
/*
JSRE is a generic JS runtime environment embedding the otto JS interpreter.
It provides some helper functions to
- load code from files
- run code snippets
- require libraries
- bind native go objects
*/
type JSRE struct {
assetPath string
vm *otto.Otto
}
func New(assetPath string) *JSRE {
re := &JSRE{
assetPath,
otto.New(),
}
// load prettyprint func definition
re.vm.Run(pp_js)
re.vm.Set("loadScript", re.loadScript)
return re
}
// Exec(file) loads and runs the contents of a file
// if a relative path is given, the jsre's assetPath is used
func (self *JSRE) Exec(file string) error {
return self.exec(common.AbsolutePath(self.assetPath, file))
}
func (self *JSRE) exec(path string) error {
code, err := ioutil.ReadFile(path)
if err != nil {
return err
}
_, err = self.vm.Run(code)
return err
}
func (self *JSRE) Bind(name string, v interface{}) (err error) {
self.vm.Set(name, v)
return
}
func (self *JSRE) Run(code string) (otto.Value, error) {
return self.vm.Run(code)
}
func (self *JSRE) Get(ns string) (otto.Value, error) {
return self.vm.Get(ns)
}
func (self *JSRE) Set(ns string, v interface{}) error {
return self.vm.Set(ns, v)
}
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
file, err := call.Argument(0).ToString()
if err != nil {
return otto.FalseValue()
}
if err := self.Exec(file); err != nil {
fmt.Println("err:", err)
return otto.FalseValue()
}
return otto.TrueValue()
}
func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
var method otto.Value
v, err = self.vm.ToValue(v)
if err != nil {
return
}
method, err = self.vm.Get("prettyPrint")
if err != nil {
return
}
return method.Call(method, v)
}
func (self *JSRE) ToVal(v interface{}) otto.Value {
result, err := self.vm.ToValue(v)
if err != nil {
fmt.Println("Value unknown:", err)
return otto.UndefinedValue()
}
return result
}
func (self *JSRE) Eval(code string) (s string, err error) {
var val otto.Value
val, err = self.Run(code)
if err != nil {
return
}
val, err = self.PrettyPrint(val)
if err != nil {
return
}
return fmt.Sprintf("%v", val), nil
}

84
jsre/jsre_test.go Normal file
View File

@ -0,0 +1,84 @@
package jsre
import (
"github.com/obscuren/otto"
"testing"
"github.com/ethereum/go-ethereum/common"
)
type testNativeObjectBinding struct {
toVal func(interface{}) otto.Value
}
type msg struct {
Msg string
}
func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value {
m, err := call.Argument(0).ToString()
if err != nil {
return otto.UndefinedValue()
}
return no.toVal(&msg{m})
}
func TestExec(t *testing.T) {
jsre := New("/tmp")
common.WriteFile("/tmp/test.js", []byte(`msg = "testMsg"`))
err := jsre.Exec("test.js")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
val, err := jsre.Run("msg")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
if !val.IsString() {
t.Errorf("expected string value, got %v", val)
}
exp := "testMsg"
got, _ := val.ToString()
if exp != got {
t.Errorf("expected '%v', got '%v'", exp, got)
}
}
func TestBind(t *testing.T) {
jsre := New("/tmp")
jsre.Bind("no", &testNativeObjectBinding{jsre.ToVal})
val, err := jsre.Run(`no.testMethod("testMsg")`)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
pp, err := jsre.PrettyPrint(val)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
t.Logf("no: %v", pp)
}
func TestLoadScript(t *testing.T) {
jsre := New("/tmp")
common.WriteFile("/tmp/test.js", []byte(`msg = "testMsg"`))
_, err := jsre.Run(`loadScript("test.js")`)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
val, err := jsre.Run("msg")
if err != nil {
t.Errorf("expected no error, got %v", err)
}
if !val.IsString() {
t.Errorf("expected string value, got %v", val)
}
exp := "testMsg"
got, _ := val.ToString()
if exp != got {
t.Errorf("expected '%v', got '%v'", exp, got)
}
}

View File

@ -1,6 +1,6 @@
package javascript package jsre
const jsLib = ` const pp_js = `
function pp(object) { function pp(object) {
var str = ""; var str = "";

View File

@ -51,9 +51,9 @@ func newTable(t transport, ourID NodeID, ourAddr *net.UDPAddr) *Table {
return tab return tab
} }
// Self returns the local node ID. // Self returns the local node.
func (tab *Table) Self() NodeID { func (tab *Table) Self() *Node {
return tab.self.ID return tab.self
} }
// Close terminates the network listener. // Close terminates the network listener.

View File

@ -180,7 +180,7 @@ func (srv *Server) Start() (err error) {
srv.ntab = ntab srv.ntab = ntab
// handshake // handshake
srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: ntab.Self()} srv.ourHandshake = &protoHandshake{Version: baseProtocolVersion, Name: srv.Name, ID: ntab.Self().ID}
for _, p := range srv.Protocols { for _, p := range srv.Protocols {
srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap()) srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap())
} }
@ -298,7 +298,7 @@ func (srv *Server) dialLoop() {
srv.lock.Lock() srv.lock.Lock()
_, isconnected := srv.peers[dest.ID] _, isconnected := srv.peers[dest.ID]
srv.lock.Unlock() srv.lock.Unlock()
if isconnected || dialing[dest.ID] || dest.ID == srv.ntab.Self() { if isconnected || dialing[dest.ID] || dest.ID == srv.Self().ID {
continue continue
} }
@ -332,12 +332,16 @@ func (srv *Server) dialNode(dest *discover.Node) {
srv.startPeer(conn, dest) srv.startPeer(conn, dest)
} }
func (srv *Server) Self() *discover.Node {
return srv.ntab.Self()
}
func (srv *Server) findPeers() { func (srv *Server) findPeers() {
far := srv.ntab.Self() far := srv.Self().ID
for i := range far { for i := range far {
far[i] = ^far[i] far[i] = ^far[i]
} }
closeToSelf := srv.ntab.Lookup(srv.ntab.Self()) closeToSelf := srv.ntab.Lookup(srv.Self().ID)
farFromSelf := srv.ntab.Lookup(far) farFromSelf := srv.ntab.Lookup(far)
for i := 0; i < len(closeToSelf) || i < len(farFromSelf); i++ { for i := 0; i < len(closeToSelf) || i < len(farFromSelf); i++ {
@ -402,7 +406,7 @@ func (srv *Server) addPeer(id discover.NodeID, p *Peer) (bool, DiscReason) {
return false, DiscTooManyPeers return false, DiscTooManyPeers
case srv.peers[id] != nil: case srv.peers[id] != nil:
return false, DiscAlreadyConnected return false, DiscAlreadyConnected
case id == srv.ntab.Self(): case id == srv.Self().ID:
return false, DiscSelf return false, DiscSelf
} }
srv.peers[id] = p srv.peers[id] = p

View File

@ -9,11 +9,11 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/event/filter" "github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/state" "github.com/ethereum/go-ethereum/state"
@ -371,6 +371,11 @@ func (p *EthereumApi) NewWhisperIdentity(reply *interface{}) error {
return nil return nil
} }
// func (p *EthereumApi) RemoveWhisperIdentity(args *WhisperIdentityArgs, reply *interface{}) error {
// *reply = p.xeth().Whisper().RemoveIdentity(args.Identity)
// return nil
// }
func (p *EthereumApi) NewWhisperFilter(args *WhisperFilterArgs, reply *interface{}) error { func (p *EthereumApi) NewWhisperFilter(args *WhisperFilterArgs, reply *interface{}) error {
var id int var id int
opts := new(xeth.Options) opts := new(xeth.Options)
@ -751,6 +756,12 @@ func (p *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) error
return p.WhisperPost(args, reply) return p.WhisperPost(args, reply)
case "shh_newIdentity": case "shh_newIdentity":
return p.NewWhisperIdentity(reply) return p.NewWhisperIdentity(reply)
// case "shh_removeIdentity":
// args := new(WhisperIdentityArgs)
// if err := json.Unmarshal(req.Params, &args); err != nil {
// return err
// }
// return p.RemoveWhisperIdentity(args, reply)
case "shh_hasIdentity": case "shh_hasIdentity":
args := new(WhisperIdentityArgs) args := new(WhisperIdentityArgs)
if err := json.Unmarshal(req.Params, &args); err != nil { if err := json.Unmarshal(req.Params, &args); err != nil {

View File

@ -331,42 +331,6 @@ func (args *Sha3Args) UnmarshalJSON(b []byte) (err error) {
return nil return nil
} }
// type FilterArgs struct {
// FromBlock uint64
// ToBlock uint64
// Limit uint64
// Offset uint64
// Address string
// Topics []string
// }
// func (args *FilterArgs) UnmarshalJSON(b []byte) (err error) {
// var obj []struct {
// FromBlock string `json:"fromBlock"`
// ToBlock string `json:"toBlock"`
// Limit string `json:"limit"`
// Offset string `json:"offset"`
// Address string `json:"address"`
// Topics []string `json:"topics"`
// }
// if err = json.Unmarshal(b, &obj); err != nil {
// return errDecodeArgs
// }
// if len(obj) < 1 {
// return errArguments
// }
// args.FromBlock = uint64(common.Big(obj[0].FromBlock).Int64())
// args.ToBlock = uint64(common.Big(obj[0].ToBlock).Int64())
// args.Limit = uint64(common.Big(obj[0].Limit).Int64())
// args.Offset = uint64(common.Big(obj[0].Offset).Int64())
// args.Address = obj[0].Address
// args.Topics = obj[0].Topics
// return nil
// }
type FilterOptions struct { type FilterOptions struct {
Earliest int64 Earliest int64
Latest int64 Latest int64
@ -378,8 +342,8 @@ type FilterOptions struct {
func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) { func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) {
var obj []struct { var obj []struct {
FromBlock string `json:"fromBlock"` FromBlock interface{} `json:"fromBlock"`
ToBlock string `json:"toBlock"` ToBlock interface{} `json:"toBlock"`
Limit string `json:"limit"` Limit string `json:"limit"`
Offset string `json:"offset"` Offset string `json:"offset"`
Address string `json:"address"` Address string `json:"address"`
@ -394,8 +358,32 @@ func (args *FilterOptions) UnmarshalJSON(b []byte) (err error) {
return NewInsufficientParamsError(len(obj), 1) return NewInsufficientParamsError(len(obj), 1)
} }
args.Earliest = int64(common.Big(obj[0].FromBlock).Int64()) fromstr, ok := obj[0].FromBlock.(string)
args.Latest = int64(common.Big(obj[0].ToBlock).Int64()) if !ok {
return NewDecodeParamError("FromBlock is not a string")
}
switch fromstr {
case "latest":
args.Earliest = 0
default:
args.Earliest = int64(common.Big(obj[0].FromBlock.(string)).Int64())
}
tostr, ok := obj[0].ToBlock.(string)
if !ok {
return NewDecodeParamError("ToBlock is not a string")
}
switch tostr {
case "latest":
args.Latest = 0
case "pending":
args.Latest = -1
default:
args.Latest = int64(common.Big(obj[0].ToBlock.(string)).Int64())
}
args.Max = int(common.Big(obj[0].Limit).Int64()) args.Max = int(common.Big(obj[0].Limit).Int64())
args.Skip = int(common.Big(obj[0].Offset).Int64()) args.Skip = int(common.Big(obj[0].Offset).Int64())
args.Address = obj[0].Address args.Address = obj[0].Address

View File

@ -74,6 +74,16 @@ func TestGetBlockByHashArgs(t *testing.T) {
} }
} }
func TestGetBlockByHashEmpty(t *testing.T) {
input := `[]`
args := new(GetBlockByHashArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestGetBlockByNumberArgs(t *testing.T) { func TestGetBlockByNumberArgs(t *testing.T) {
input := `["0x1b4", false]` input := `["0x1b4", false]`
expected := new(GetBlockByNumberArgs) expected := new(GetBlockByNumberArgs)
@ -94,6 +104,16 @@ func TestGetBlockByNumberArgs(t *testing.T) {
} }
} }
func TestGetBlockByNumberEmpty(t *testing.T) {
input := `[]`
args := new(GetBlockByNumberArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestNewTxArgs(t *testing.T) { func TestNewTxArgs(t *testing.T) {
input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", input := `[{"from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
"to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675", "to": "0xd46e8dd67c5d32be8058bb8eb970870f072445675",
@ -139,6 +159,16 @@ func TestNewTxArgs(t *testing.T) {
} }
} }
func TestNewTxArgsEmpty(t *testing.T) {
input := `[]`
args := new(NewTxArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestGetStorageArgs(t *testing.T) { func TestGetStorageArgs(t *testing.T) {
input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]`
expected := new(GetStorageArgs) expected := new(GetStorageArgs)
@ -163,6 +193,16 @@ func TestGetStorageArgs(t *testing.T) {
} }
} }
func TestGetStorageEmptyArgs(t *testing.T) {
input := `[]`
args := new(GetStorageArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestGetStorageAtArgs(t *testing.T) { func TestGetStorageAtArgs(t *testing.T) {
input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x0", "0x2"]` input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "0x0", "0x2"]`
expected := new(GetStorageAtArgs) expected := new(GetStorageAtArgs)
@ -192,6 +232,16 @@ func TestGetStorageAtArgs(t *testing.T) {
} }
} }
func TestGetStorageAtEmptyArgs(t *testing.T) {
input := `[]`
args := new(GetStorageAtArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestGetTxCountArgs(t *testing.T) { func TestGetTxCountArgs(t *testing.T) {
input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]` input := `["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]`
expected := new(GetTxCountArgs) expected := new(GetTxCountArgs)
@ -216,6 +266,16 @@ func TestGetTxCountArgs(t *testing.T) {
} }
} }
func TestGetTxCountEmptyArgs(t *testing.T) {
input := `[]`
args := new(GetTxCountArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestGetDataArgs(t *testing.T) { func TestGetDataArgs(t *testing.T) {
input := `["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", "latest"]` input := `["0xd5677cf67b5aa051bb40496e68ad359eb97cfbf8", "latest"]`
expected := new(GetDataArgs) expected := new(GetDataArgs)
@ -240,6 +300,16 @@ func TestGetDataArgs(t *testing.T) {
} }
} }
func TestGetDataEmptyArgs(t *testing.T) {
input := `[]`
args := new(GetDataArgs)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestFilterOptions(t *testing.T) { func TestFilterOptions(t *testing.T) {
input := `[{ input := `[{
"fromBlock": "0x1", "fromBlock": "0x1",
@ -286,6 +356,56 @@ func TestFilterOptions(t *testing.T) {
// } // }
} }
func TestFilterOptionsWords(t *testing.T) {
input := `[{
"fromBlock": "latest",
"toBlock": "pending"
}]`
expected := new(FilterOptions)
expected.Earliest = 0
expected.Latest = -1
args := new(FilterOptions)
if err := json.Unmarshal([]byte(input), &args); err != nil {
t.Error(err)
}
if expected.Earliest != args.Earliest {
t.Errorf("Earliest shoud be %#v but is %#v", expected.Earliest, args.Earliest)
}
if expected.Latest != args.Latest {
t.Errorf("Latest shoud be %#v but is %#v", expected.Latest, args.Latest)
}
}
func TestFilterOptionsNums(t *testing.T) {
input := `[{
"fromBlock": 2,
"toBlock": 3
}]`
args := new(FilterOptions)
err := json.Unmarshal([]byte(input), &args)
switch err.(type) {
case *DecodeParamError:
break
default:
t.Errorf("Should have *DecodeParamError but instead have %T", err)
}
}
func TestFilterOptionsEmptyArgs(t *testing.T) {
input := `[]`
args := new(FilterOptions)
err := json.Unmarshal([]byte(input), &args)
if err == nil {
t.Error("Expected error but didn't get one")
}
}
func TestDbArgs(t *testing.T) { func TestDbArgs(t *testing.T) {
input := `["0x74657374","0x6b6579","0x6d79537472696e67"]` input := `["0x74657374","0x6b6579","0x6d79537472696e67"]`
expected := new(DbArgs) expected := new(DbArgs)

View File

@ -26,7 +26,7 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler {
if req.ContentLength > maxSizeReqLength { if req.ContentLength > maxSizeReqLength {
jsonerr := &RpcErrorObject{-32700, "Request too large"} jsonerr := &RpcErrorObject{-32700, "Request too large"}
json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) json.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
return return
} }
@ -36,11 +36,11 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler {
break break
case *DecodeParamError, *InsufficientParamsError, *ValidationError: case *DecodeParamError, *InsufficientParamsError, *ValidationError:
jsonerr := &RpcErrorObject{-32602, reqerr.Error()} jsonerr := &RpcErrorObject{-32602, reqerr.Error()}
json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) json.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
return return
default: default:
jsonerr := &RpcErrorObject{-32700, "Could not parse request"} jsonerr := &RpcErrorObject{-32700, "Could not parse request"}
json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: nil, Error: jsonerr}) json.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr})
return return
} }
@ -51,19 +51,19 @@ func JSONRPC(pipe *xeth.XEth, dataDir string) http.Handler {
break break
case *NotImplementedError: case *NotImplementedError:
jsonerr := &RpcErrorObject{-32601, reserr.Error()} jsonerr := &RpcErrorObject{-32601, reserr.Error()}
json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) json.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: reqParsed.Id, Error: jsonerr})
return return
case *DecodeParamError, *InsufficientParamsError, *ValidationError: case *DecodeParamError, *InsufficientParamsError, *ValidationError:
jsonerr := &RpcErrorObject{-32602, reserr.Error()} jsonerr := &RpcErrorObject{-32602, reserr.Error()}
json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) json.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: reqParsed.Id, Error: jsonerr})
return return
default: default:
jsonerr := &RpcErrorObject{-32603, reserr.Error()} jsonerr := &RpcErrorObject{-32603, reserr.Error()}
json.Send(w, &RpcErrorResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Error: jsonerr}) json.Send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: reqParsed.Id, Error: jsonerr})
return return
} }
rpchttplogger.DebugDetailf("Generated response: %T %s", response, response) rpchttplogger.DebugDetailf("Generated response: %T %s", response, response)
json.Send(w, &RpcSuccessResponse{JsonRpc: jsonrpcver, ID: reqParsed.ID, Result: response}) json.Send(w, &RpcSuccessResponse{Jsonrpc: jsonrpcver, Id: reqParsed.Id, Result: response})
}) })
} }

43
rpc/jeth.go Normal file
View File

@ -0,0 +1,43 @@
package rpc
import (
"encoding/json"
// "fmt"
"github.com/obscuren/otto"
)
type Jeth struct {
ethApi *EthereumApi
toVal func(interface{}) otto.Value
}
func NewJeth(ethApi *EthereumApi, toVal func(interface{}) otto.Value) *Jeth {
return &Jeth{ethApi, toVal}
}
func (self *Jeth) err(code int, msg string, id interface{}) otto.Value {
rpcerr := &RpcErrorObject{code, msg}
rpcresponse := &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: id, Error: rpcerr}
return self.toVal(rpcresponse)
}
func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
reqif, err := call.Argument(0).Export()
if err != nil {
return self.err(-32700, err.Error(), nil)
}
jsonreq, err := json.Marshal(reqif)
var req RpcRequest
err = json.Unmarshal(jsonreq, &req)
var respif interface{}
err = self.ethApi.GetRequestReply(&req, &respif)
if err != nil {
return self.err(-32603, err.Error(), req.Id)
}
rpcresponse := &RpcSuccessResponse{Jsonrpc: jsonrpcver, Id: req.Id, Result: respif}
response = self.toVal(rpcresponse)
return
}

View File

@ -83,21 +83,21 @@ func NewValidationError(param string, msg string) error {
} }
type RpcRequest struct { type RpcRequest struct {
ID interface{} `json:"id"` Id interface{} `json:"id"`
JsonRpc string `json:"jsonrpc"` Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"` Method string `json:"method"`
Params json.RawMessage `json:"params"` Params json.RawMessage `json:"params"`
} }
type RpcSuccessResponse struct { type RpcSuccessResponse struct {
ID interface{} `json:"id"` Id interface{} `json:"id"`
JsonRpc string `json:"jsonrpc"` Jsonrpc string `json:"jsonrpc"`
Result interface{} `json:"result"` Result interface{} `json:"result"`
} }
type RpcErrorResponse struct { type RpcErrorResponse struct {
ID interface{} `json:"id"` Id interface{} `json:"id"`
JsonRpc string `json:"jsonrpc"` Jsonrpc string `json:"jsonrpc"`
Error *RpcErrorObject `json:"error"` Error *RpcErrorObject `json:"error"`
} }

View File

@ -45,6 +45,11 @@ func UnmarshalRawMessages(b []byte, iface interface{}, number *int64) (err error
return NewDecodeParamError(err.Error()) return NewDecodeParamError(err.Error())
} }
// Hrm... Occurs when no params
if len(data) == 0 {
return NewDecodeParamError("No data")
}
// Number index determines the index in the array for a possible block number // Number index determines the index in the array for a possible block number
numberIndex := 0 numberIndex := 0

View File

@ -116,6 +116,15 @@ func (self *Whisper) GetIdentity(key *ecdsa.PublicKey) *ecdsa.PrivateKey {
return self.keys[string(crypto.FromECDSAPub(key))] return self.keys[string(crypto.FromECDSAPub(key))]
} }
// func (self *Whisper) RemoveIdentity(key *ecdsa.PublicKey) bool {
// k := string(crypto.FromECDSAPub(key))
// if _, ok := self.keys[k]; ok {
// delete(self.keys, k)
// return true
// }
// return false
// }
func (self *Whisper) Watch(opts Filter) int { func (self *Whisper) Watch(opts Filter) int {
return self.filters.Install(filter.Generic{ return self.filters.Install(filter.Generic{
Str1: string(crypto.FromECDSAPub(opts.To)), Str1: string(crypto.FromECDSAPub(opts.To)),

View File

@ -4,8 +4,8 @@ import (
"errors" "errors"
"time" "time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/whisper"
) )
@ -63,6 +63,10 @@ func (self *Whisper) HasIdentity(key string) bool {
return self.Whisper.HasIdentity(crypto.ToECDSAPub(common.FromHex(key))) return self.Whisper.HasIdentity(crypto.ToECDSAPub(common.FromHex(key)))
} }
// func (self *Whisper) RemoveIdentity(key string) bool {
// return self.Whisper.RemoveIdentity(crypto.ToECDSAPub(common.FromHex(key)))
// }
func (self *Whisper) Watch(opts *Options) int { func (self *Whisper) Watch(opts *Options) int {
filter := whisper.Filter{ filter := whisper.Filter{
To: crypto.ToECDSAPub(common.FromHex(opts.To)), To: crypto.ToECDSAPub(common.FromHex(opts.To)),