From 132e87a707a97fcc3af759a0cec6c1efbdd25eb9 Mon Sep 17 00:00:00 2001
From: zelig <viktor.tron@gmail.com>
Date: Sun, 15 Mar 2015 13:43:48 +0700
Subject: [PATCH] node admin interface for Frontier Console, see spec
 https://github.com/ethereum/go-ethereum/wiki/Frontier-Console

---
 cmd/ethereum/admin.go   | 259 +++++++++++++++++++++++++++++++++++
 cmd/ethereum/js.go      |   1 +
 cmd/ethereum/js_test.go | 290 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 550 insertions(+)
 create mode 100644 cmd/ethereum/admin.go
 create mode 100644 cmd/ethereum/js_test.go

diff --git a/cmd/ethereum/admin.go b/cmd/ethereum/admin.go
new file mode 100644
index 000000000..2f95bc3cb
--- /dev/null
+++ b/cmd/ethereum/admin.go
@@ -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/ethutil"
+	"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(ethutil.FromHex(split[0]), split[1])
+	// if err != nil {
+	// 	utils.Fatalf("Unlock account failed '%v'", err)
+	// }
+	err = am.TimedUnlock(ethutil.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(ethutil.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 := ethutil.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(ethutil.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)
+
+}
diff --git a/cmd/ethereum/js.go b/cmd/ethereum/js.go
index 361aaf7ea..b4b54b7e6 100644
--- a/cmd/ethereum/js.go
+++ b/cmd/ethereum/js.go
@@ -71,6 +71,7 @@ func newJSRE(ethereum *eth.Ethereum, libPath string) *jsre {
 	js.xeth = xeth.New(ethereum, js)
 	js.re = re.New(libPath)
 	js.apiBindings()
+	js.adminBindings()
 
 	if !liner.TerminalSupported() {
 		js.prompter = dumbterm{bufio.NewReader(os.Stdin)}
diff --git a/cmd/ethereum/js_test.go b/cmd/ethereum/js_test.go
new file mode 100644
index 000000000..132101912
--- /dev/null
+++ b/cmd/ethereum/js_test.go
@@ -0,0 +1,290 @@
+package main
+
+import (
+	"fmt"
+	"github.com/obscuren/otto"
+	"os"
+	"path"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/ethutil"
+)
+
+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")
+	ethutil.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")
+	}
+
+	val, err = repl.re.Run("admin.startMining(4)")
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	}
+	mining, _ = val.ToBoolean()
+	if !mining {
+		t.Errorf("expected true (mining), got false")
+	}
+	val, err = repl.re.Run("eth.mining")
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	}
+	mining, err = val.ToBoolean()
+	if err != nil {
+		t.Errorf("expected boolean, got %v", err)
+	}
+	if !mining {
+		t.Errorf("expected true (mining), got false")
+	}
+
+	val, err = repl.re.Run("admin.startMining(4)")
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	}
+	mining, _ = val.ToBoolean()
+	if !mining {
+		t.Errorf("expected true (mining), got false")
+	}
+
+	val, err = repl.re.Run("admin.stopMining()")
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	}
+	mining, _ = val.ToBoolean()
+	if !mining {
+		t.Errorf("expected true (mining), got false")
+	}
+
+}
+
+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")
+	}
+}
-- 
GitLab