rpc: migrated the RPC insterface to a new reflection based RPC layer

This commit is contained in:
Bas van Kervel
2015-12-16 10:58:01 +01:00
committed by Jeffrey Wilcke
parent f2ab351e8d
commit 19b2640e89
132 changed files with 4711 additions and 14320 deletions

View File

@ -24,23 +24,16 @@ import (
"os/signal"
"path/filepath"
"regexp"
"strings"
"sort"
"strings"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/natspec"
"github.com/ethereum/go-ethereum/common/registrar"
"github.com/ethereum/go-ethereum/eth"
re "github.com/ethereum/go-ethereum/jsre"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/xeth"
"github.com/peterh/liner"
"github.com/robertkrimen/otto"
)
@ -79,82 +72,90 @@ func (r dumbterm) AppendHistory(string) {}
type jsre struct {
re *re.JSRE
stack *node.Node
xeth *xeth.XEth
wait chan *big.Int
ps1 string
atexit func()
corsDomain string
client comms.EthereumClient
client rpc.Client
prompter
}
var (
loadedModulesMethods map[string][]string
loadedModulesMethods map[string][]string
autoCompleteStatement = "function _autocomplete(obj) {var results = []; for (var e in obj) { results.push(e); }; return results; }; _autocomplete(%s)"
)
func keywordCompleter(line string) []string {
results := make([]string, 0)
func keywordCompleter(jsre *jsre, line string) []string {
var results []string
parts := strings.Split(line, ".")
objRef := "this"
prefix := line
if len(parts) > 1 {
objRef = strings.Join(parts[0:len(parts) - 1], ".")
prefix = parts[len(parts) - 1]
}
if strings.Contains(line, ".") {
elements := strings.Split(line, ".")
if len(elements) == 2 {
module := elements[0]
partialMethod := elements[1]
if methods, found := loadedModulesMethods[module]; found {
for _, method := range methods {
if strings.HasPrefix(method, partialMethod) { // e.g. debug.se
results = append(results, module+"."+method)
}
result, _ := jsre.re.Run(fmt.Sprintf(autoCompleteStatement, objRef))
raw, _ := result.Export()
if keys, ok := raw.([]interface{}); ok {
for _, k := range keys {
if strings.HasPrefix(fmt.Sprintf("%s", k), prefix) {
if objRef == "this" {
results = append(results, fmt.Sprintf("%s", k))
} else {
results = append(results, fmt.Sprintf("%s.%s", strings.Join(parts[:len(parts) - 1], "."), k))
}
}
}
} else {
for module, methods := range loadedModulesMethods {
if line == module { // user typed in full module name, show all methods
for _, method := range methods {
results = append(results, module+"."+method)
}
} else if strings.HasPrefix(module, line) { // partial method name, e.g. admi
results = append(results, module)
}
}
}
// e.g. web3<tab><tab> append dot since its an object
isObj, _ := jsre.re.Run(fmt.Sprintf("typeof(%s) === 'object'", line))
if isObject, _ := isObj.ToBoolean(); isObject {
results = append(results, line + ".")
}
sort.Strings(results)
return results
}
func apiWordCompleter(line string, pos int) (head string, completions []string, tail string) {
if len(line) == 0 || pos == 0 {
return "", nil, ""
func apiWordCompleterWithContext(jsre *jsre) liner.WordCompleter {
completer := func(line string, pos int) (head string, completions []string, tail string) {
if len(line) == 0 || pos == 0 {
return "", nil, ""
}
// chuck data to relevant part for autocompletion, e.g. in case of nested lines eth.getBalance(eth.coinb<tab><tab>
i := 0
for i = pos - 1; i > 0; i-- {
if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') {
continue
}
if i >= 3 && line[i] == '3' && line[i - 3] == 'w' && line[i - 2] == 'e' && line[i - 1] == 'b' {
continue
}
i += 1
break
}
begin := line[:i]
keyword := line[i:pos]
end := line[pos:]
completionWords := keywordCompleter(jsre, keyword)
return begin, completionWords, end
}
i := 0
for i = pos - 1; i > 0; i-- {
if line[i] == '.' || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= 'A' && line[i] <= 'Z') {
continue
}
if i >= 3 && line[i] == '3' && line[i-3] == 'w' && line[i-2] == 'e' && line[i-1] == 'b' {
continue
}
i += 1
break
}
begin := line[:i]
keyword := line[i:pos]
end := line[pos:]
completionWords := keywordCompleter(keyword)
return begin, completionWords, end
return completer
}
func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir string, interactive bool) *jsre {
func newLightweightJSRE(docRoot string, client rpc.Client, datadir string, interactive bool) *jsre {
js := &jsre{ps1: "> "}
js.wait = make(chan *big.Int)
js.client = client
// update state in separare forever blocks
js.re = re.New(docRoot)
if err := js.apiBindings(js); err != nil {
if err := js.apiBindings(); err != nil {
utils.Fatalf("Unable to initialize console - %v", err)
}
@ -165,7 +166,7 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str
js.withHistory(datadir, func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true)
js.loadAutoCompletion()
lr.SetWordCompleter(apiWordCompleter)
lr.SetWordCompleter(apiWordCompleterWithContext(js))
lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr
js.atexit = func() {
@ -177,25 +178,15 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str
return js
}
func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
func newJSRE(stack *node.Node, docRoot, corsDomain string, client rpc.Client, interactive bool) *jsre {
js := &jsre{stack: stack, ps1: "> "}
// set default cors domain used by startRpc from CLI flag
js.corsDomain = corsDomain
if f == nil {
f = js
}
js.xeth = xeth.New(stack, f)
js.wait = js.xeth.UpdateState()
js.wait = make(chan *big.Int)
js.client = client
if clt, ok := js.client.(*comms.InProcClient); ok {
if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, stack); err == nil {
clt.Initialize(api.Merge(offeredApis...))
}
}
// update state in separare forever blocks
js.re = re.New(docRoot)
if err := js.apiBindings(f); err != nil {
if err := js.apiBindings(); err != nil {
utils.Fatalf("Unable to connect - %v", err)
}
@ -206,7 +197,7 @@ func newJSRE(stack *node.Node, docRoot, corsDomain string, client comms.Ethereum
js.withHistory(stack.DataDir(), func(hist *os.File) { lr.ReadHistory(hist) })
lr.SetCtrlCAborts(true)
js.loadAutoCompletion()
lr.SetWordCompleter(apiWordCompleter)
lr.SetWordCompleter(apiWordCompleterWithContext(js))
lr.SetTabCompletionStyle(liner.TabPrints)
js.prompter = lr
js.atexit = func() {
@ -222,7 +213,7 @@ func (self *jsre) loadAutoCompletion() {
if modules, err := self.supportedApis(); err == nil {
loadedModulesMethods = make(map[string][]string)
for module, _ := range modules {
loadedModulesMethods[module] = api.AutoCompletion[module]
loadedModulesMethods[module] = rpc.AutoCompletion[module]
}
}
}
@ -258,7 +249,6 @@ func (self *jsre) welcome() {
loadedModules = append(loadedModules, fmt.Sprintf("%s:%s", api, version))
}
sort.Strings(loadedModules)
}
}
@ -266,7 +256,7 @@ func (self *jsre) supportedApis() (map[string]string, error) {
return self.client.SupportedModules()
}
func (js *jsre) apiBindings(f xeth.Frontend) error {
func (js *jsre) apiBindings() error {
apis, err := js.supportedApis()
if err != nil {
return err
@ -277,12 +267,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
apiNames = append(apiNames, a)
}
apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.stack)
if err != nil {
utils.Fatalf("Unable to determine supported api's: %v", err)
}
jeth := rpc.NewJeth(api.Merge(apiImpl...), js.re, js.client, f)
jeth := utils.NewJeth(js.re, js.client)
js.re.Set("jeth", struct{}{})
t, _ := js.re.Get("jeth")
jethObj := t.Object()
@ -313,14 +298,16 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
// load only supported API's in javascript runtime
shortcuts := "var eth = web3.eth; "
for _, apiName := range apiNames {
if apiName == shared.Web3ApiName {
continue // manually mapped
if apiName == "web3" || apiName == "rpc" {
continue // manually mapped or ignore
}
if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), api.Javascript(apiName)); err == nil {
shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName)
} else {
utils.Fatalf("Error loading %s.js: %v", apiName, err)
if jsFile, ok := rpc.WEB3Extensions[apiName]; ok {
if err = js.re.Compile(fmt.Sprintf("%s.js", apiName), jsFile); err == nil {
shortcuts += fmt.Sprintf("var %s = web3.%s; ", apiName, apiName)
} else {
utils.Fatalf("Error loading %s.js: %v", apiName, err)
}
}
}
@ -375,14 +362,13 @@ func (self *jsre) ConfirmTransaction(tx string) bool {
return false
}
// If natspec is enabled, ask for permission
if ethereum.NatSpec {
notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient())
fmt.Println(notice)
answer, _ := self.Prompt("Confirm Transaction [y/n]")
return strings.HasPrefix(strings.Trim(answer, " "), "y")
} else {
return true
if ethereum.NatSpec && false /* disabled for now */ {
// notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient())
// fmt.Println(notice)
// answer, _ := self.Prompt("Confirm Transaction [y/n]")
// return strings.HasPrefix(strings.Trim(answer, " "), "y")
}
return true
}
func (self *jsre) UnlockAccount(addr []byte) bool {

View File

@ -32,30 +32,27 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/common/httpclient"
"github.com/ethereum/go-ethereum/common/natspec"
"github.com/ethereum/go-ethereum/common/registrar"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/cmd/utils"
)
const (
testSolcPath = ""
solcVersion = "0.9.23"
solcVersion = "0.9.23"
testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674"
testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testBalance = "10000000000000000000"
// of empty string
// of empty string
testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
)
var (
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f"))
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
)
@ -77,15 +74,16 @@ func (self *testjethre) UnlockAccount(acc []byte) bool {
return true
}
func (self *testjethre) ConfirmTransaction(tx string) bool {
var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
if ethereum.NatSpec {
self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
}
return true
}
// Temporary disabled while natspec hasn't been migrated
//func (self *testjethre) ConfirmTransaction(tx string) bool {
// var ethereum *eth.Ethereum
// self.stack.Service(&ethereum)
//
// if ethereum.NatSpec {
// self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
// }
// return true
//}
func testJEthRE(t *testing.T) (string, *testjethre, *node.Node) {
return testREPL(t, nil)
@ -118,7 +116,9 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod
if config != nil {
config(ethConf)
}
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return eth.New(ctx, ethConf)
}); err != nil {
t.Fatalf("failed to register ethereum protocol: %v", err)
}
// Initialize all the keys for testing
@ -141,9 +141,10 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod
stack.Service(&ethereum)
assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
client := comms.NewInProcClient(codec.JSON)
//client := comms.NewInProcClient(codec.JSON)
client := utils.NewInProcRPCClient(stack)
tf := &testjethre{client: ethereum.HTTPClient()}
repl := newJSRE(stack, assetPath, "", client, false, tf)
repl := newJSRE(stack, assetPath, "", client, false)
tf.jsre = repl
return tmp, tf, stack
}
@ -166,8 +167,8 @@ func TestAccounts(t *testing.T) {
defer node.Stop()
defer os.RemoveAll(tmp)
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`"]`)
checkEvalJSON(t, repl, `eth.coinbase`, `"`+testAddress+`"`)
checkEvalJSON(t, repl, `eth.accounts`, `["` + testAddress + `"]`)
checkEvalJSON(t, repl, `eth.coinbase`, `"` + testAddress + `"`)
val, err := repl.re.Run(`jeth.newAccount("password")`)
if err != nil {
t.Errorf("expected no error, got %v", err)
@ -177,7 +178,7 @@ func TestAccounts(t *testing.T) {
t.Errorf("address not hex: %q", addr)
}
checkEvalJSON(t, repl, `eth.accounts`, `["`+testAddress+`","`+addr+`"]`)
checkEvalJSON(t, repl, `eth.accounts`, `["` + testAddress + `","` + addr + `"]`)
}
@ -205,13 +206,13 @@ func TestBlockChain(t *testing.T) {
node.Service(&ethereum)
ethereum.BlockChain().Reset()
checkEvalJSON(t, repl, `admin.exportChain(`+tmpfileq+`)`, `true`)
checkEvalJSON(t, repl, `admin.exportChain(` + tmpfileq + `)`, `true`)
if _, err := os.Stat(tmpfile); err != nil {
t.Fatal(err)
}
// check import, verify that dumpBlock gives the same result.
checkEvalJSON(t, repl, `admin.importChain(`+tmpfileq+`)`, `true`)
checkEvalJSON(t, repl, `admin.importChain(` + tmpfileq + `)`, `true`)
checkEvalJSON(t, repl, `debug.dumpBlock(eth.blockNumber)`, beforeExport)
}
@ -239,7 +240,7 @@ func TestCheckTestAccountBalance(t *testing.T) {
defer os.RemoveAll(tmp)
repl.re.Run(`primary = "` + testAddress + `"`)
checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"`+testBalance+`"`)
checkEvalJSON(t, repl, `eth.getBalance(primary)`, `"` + testBalance + `"`)
}
func TestSignature(t *testing.T) {
@ -278,19 +279,20 @@ func TestContract(t *testing.T) {
defer ethereum.Stop()
defer os.RemoveAll(tmp)
reg := registrar.New(repl.xeth)
_, err := reg.SetGlobalRegistrar("", coinbase)
if err != nil {
t.Errorf("error setting HashReg: %v", err)
}
_, err = reg.SetHashReg("", coinbase)
if err != nil {
t.Errorf("error setting HashReg: %v", err)
}
_, err = reg.SetUrlHint("", coinbase)
if err != nil {
t.Errorf("error setting HashReg: %v", err)
}
// Temporary disabled while registrar isn't migrated
//reg := registrar.New(repl.xeth)
//_, err := reg.SetGlobalRegistrar("", coinbase)
//if err != nil {
// t.Errorf("error setting HashReg: %v", err)
//}
//_, err = reg.SetHashReg("", coinbase)
//if err != nil {
// t.Errorf("error setting HashReg: %v", err)
//}
//_, err = reg.SetUrlHint("", coinbase)
//if err != nil {
// t.Errorf("error setting HashReg: %v", err)
//}
/* TODO:
* lookup receipt and contract addresses by tx hash
* name registration for HashReg and UrlHint addresses
@ -299,11 +301,11 @@ func TestContract(t *testing.T) {
*/
source := `contract test {\n` +
" /// @notice Will multiply `a` by 7." + `\n` +
` function multiply(uint a) returns(uint d) {\n` +
` return a * 7;\n` +
` }\n` +
`}\n`
" /// @notice Will multiply `a` by 7." + `\n` +
` function multiply(uint a) returns(uint d) {\n` +
` return a * 7;\n` +
` }\n` +
`}\n`
if checkEvalJSON(t, repl, `admin.stopNatSpec()`, `true`) != nil {
return
@ -313,10 +315,10 @@ func TestContract(t *testing.T) {
if err != nil {
t.Fatalf("%v", err)
}
if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"`+testAddress+`"`) != nil {
if checkEvalJSON(t, repl, `primary = eth.accounts[0]`, `"` + testAddress + `"`) != nil {
return
}
if checkEvalJSON(t, repl, `source = "`+source+`"`, `"`+source+`"`) != nil {
if checkEvalJSON(t, repl, `source = "` + source + `"`, `"` + source + `"`) != nil {
return
}
@ -394,7 +396,7 @@ multiply7 = Multiply7.at(contractaddress);
var contentHash = `"0x86d2b7cf1e72e9a7a3f8d96601f0151742a2f780f1526414304fbe413dc7f9bd"`
if sol != nil && solcVersion != sol.Version() {
modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`))
modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"` + sol.Version() + `"`))
fmt.Printf("modified contractinfo:\n%s\n", modContractInfo)
contentHash = `"` + common.ToHex(crypto.Sha3([]byte(modContractInfo))) + `"`
}
@ -474,11 +476,12 @@ func processTxs(repl *testjethre, t *testing.T, expTxc int) bool {
defer ethereum.StopMining()
timer := time.NewTimer(100 * time.Second)
height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1))
blockNr := ethereum.BlockChain().CurrentBlock().Number()
height := new(big.Int).Add(blockNr, big.NewInt(1))
repl.wait <- height
select {
case <-timer.C:
// if times out make sure the xeth loop does not block
// if times out make sure the xeth loop does not block
go func() {
select {
case repl.wait <- nil:

View File

@ -40,8 +40,6 @@ import (
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
)
const (
@ -263,11 +261,11 @@ See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console
Name: "attach",
Usage: `Geth Console: interactive JavaScript environment (connect to node)`,
Description: `
The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API.
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
This command allows to open a console on a running geth node.
`,
The Geth console is an interactive shell for the JavaScript runtime environment
which exposes a node admin interface as well as the Ðapp JavaScript API.
See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Console.
This command allows to open a console on a running geth node.
`,
},
{
Action: execScripts,
@ -309,11 +307,15 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.RPCEnabledFlag,
utils.RPCListenAddrFlag,
utils.RPCPortFlag,
utils.RpcApiFlag,
utils.RPCApiFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
utils.WSPortFlag,
utils.WSApiFlag,
utils.WSAllowedDomainsFlag,
utils.IPCDisabledFlag,
utils.IPCApiFlag,
utils.IPCPathFlag,
utils.IPCExperimental,
utils.ExecFlag,
utils.WhisperEnabledFlag,
utils.DevModeFlag,
@ -392,20 +394,12 @@ func geth(ctx *cli.Context) {
node.Wait()
}
// attach will connect to a running geth instance attaching a JavaScript console and to it.
func attach(ctx *cli.Context) {
var client comms.EthereumClient
var err error
if ctx.Args().Present() {
client, err = comms.ClientFromEndpoint(ctx.Args().First(), codec.JSON)
} else {
cfg := comms.IpcConfig{
Endpoint: utils.IpcSocketPath(ctx),
}
client, err = comms.NewIpcClient(cfg, codec.JSON)
}
// attach to a running geth instance
client, err := utils.NewRemoteRPCClient(ctx)
if err != nil {
utils.Fatalf("Unable to attach to geth node - %v", err)
utils.Fatalf("Unable to attach to geth - %v", err)
}
repl := newLightweightJSRE(
@ -431,11 +425,12 @@ func console(ctx *cli.Context) {
startNode(ctx, node)
// Attach to the newly started node, and either execute script or become interactive
client := comms.NewInProcClient(codec.JSON)
client := utils.NewInProcRPCClient(node)
repl := newJSRE(node,
ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
client, true, nil)
client, true)
if script := ctx.GlobalString(utils.ExecFlag.Name); script != "" {
repl.batch(script)
@ -454,11 +449,12 @@ func execScripts(ctx *cli.Context) {
startNode(ctx, node)
// Attach to the newly started node and execute the given scripts
client := comms.NewInProcClient(codec.JSON)
client := utils.NewInProcRPCClient(node)
repl := newJSRE(node,
ctx.GlobalString(utils.JSpathFlag.Name),
ctx.GlobalString(utils.RPCCORSDomainFlag.Name),
client, false, nil)
client, false)
for _, file := range ctx.Args() {
repl.exec(file)
@ -517,6 +513,11 @@ func startNode(ctx *cli.Context, stack *node.Node) {
utils.Fatalf("Failed to start RPC: %v", err)
}
}
if ctx.GlobalBool(utils.WSEnabledFlag.Name) {
if err := utils.StartWS(stack, ctx); err != nil {
utils.Fatalf("Failed to start WS: %v", err)
}
}
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil {
utils.Fatalf("Failed to start mining: %v", err)

View File

@ -21,16 +21,15 @@ import (
"math"
"reflect"
"runtime"
"sort"
"strings"
"time"
"sort"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/gizak/termui"
)
@ -70,20 +69,18 @@ to display multiple metrics simultaneously.
// monitor starts a terminal UI based monitoring tool for the requested metrics.
func monitor(ctx *cli.Context) {
var (
client comms.EthereumClient
client rpc.Client
err error
)
// Attach to an Ethereum node over IPC or RPC
endpoint := ctx.String(monitorCommandAttachFlag.Name)
if client, err = comms.ClientFromEndpoint(endpoint, codec.JSON); err != nil {
if client, err = utils.NewRemoteRPCClientFromString(endpoint); err != nil {
utils.Fatalf("Unable to attach to geth node: %v", err)
}
defer client.Close()
xeth := rpc.NewXeth(client)
// Retrieve all the available metrics and resolve the user pattens
metrics, err := retrieveMetrics(xeth)
metrics, err := retrieveMetrics(client)
if err != nil {
utils.Fatalf("Failed to retrieve system metrics: %v", err)
}
@ -133,7 +130,7 @@ func monitor(ctx *cli.Context) {
}
termui.Body.AddRows(termui.NewRow(termui.NewCol(12, 0, footer)))
refreshCharts(xeth, monitored, data, units, charts, ctx, footer)
refreshCharts(client, monitored, data, units, charts, ctx, footer)
termui.Body.Align()
termui.Render(termui.Body)
@ -154,7 +151,7 @@ func monitor(ctx *cli.Context) {
termui.Render(termui.Body)
}
case <-refresh:
if refreshCharts(xeth, monitored, data, units, charts, ctx, footer) {
if refreshCharts(client, monitored, data, units, charts, ctx, footer) {
termui.Body.Align()
}
termui.Render(termui.Body)
@ -164,8 +161,30 @@ func monitor(ctx *cli.Context) {
// retrieveMetrics contacts the attached geth node and retrieves the entire set
// of collected system metrics.
func retrieveMetrics(xeth *rpc.Xeth) (map[string]interface{}, error) {
return xeth.Call("debug_metrics", []interface{}{true})
func retrieveMetrics(client rpc.Client) (map[string]interface{}, error) {
req := map[string]interface{}{
"id": new(int64),
"method": "debug_metrics",
"jsonrpc": "2.0",
"params": []interface{}{true},
}
if err := client.Send(req); err != nil {
return nil, err
}
var res rpc.JSONSuccessResponse
if err := client.Recv(&res); err != nil {
return nil, err
}
if res.Result != nil {
if mets, ok := res.Result.(map[string]interface{}); ok {
return mets, nil
}
}
return nil, fmt.Errorf("unable to retrieve metrics")
}
// resolveMetrics takes a list of input metric patterns, and resolves each to one
@ -253,8 +272,8 @@ func fetchMetric(metrics map[string]interface{}, metric string) float64 {
// refreshCharts retrieves a next batch of metrics, and inserts all the new
// values into the active datasets and charts
func refreshCharts(xeth *rpc.Xeth, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) {
values, err := retrieveMetrics(xeth)
func refreshCharts(client rpc.Client, metrics []string, data [][]float64, units []int, charts []*termui.LineChart, ctx *cli.Context, footer *termui.Par) (realign bool) {
values, err := retrieveMetrics(client)
for i, metric := range metrics {
if len(data) < 512 {
data[i] = append([]float64{fetchMetric(values, metric)}, data[i]...)

View File

@ -87,7 +87,12 @@ var AppHelpFlagGroups = []flagGroup{
utils.RPCEnabledFlag,
utils.RPCListenAddrFlag,
utils.RPCPortFlag,
utils.RpcApiFlag,
utils.RPCApiFlag,
utils.WSEnabledFlag,
utils.WSListenAddrFlag,
utils.WSPortFlag,
utils.WSApiFlag,
utils.WSAllowedDomainsFlag,
utils.IPCDisabledFlag,
utils.IPCApiFlag,
utils.IPCPathFlag,
@ -158,7 +163,6 @@ var AppHelpFlagGroups = []flagGroup{
Flags: []cli.Flag{
utils.WhisperEnabledFlag,
utils.NatspecEnabledFlag,
utils.IPCExperimental,
},
},
{

View File

@ -26,8 +26,9 @@ import (
"path/filepath"
"runtime"
"errors"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
@ -35,13 +36,9 @@ import (
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/tests"
"github.com/ethereum/go-ethereum/whisper"
"github.com/ethereum/go-ethereum/xeth"
)
const defaultTestKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
@ -176,21 +173,25 @@ func RunTest(stack *node.Node, test *tests.BlockTest) error {
// StartRPC initializes an RPC interface to the given protocol stack.
func StartRPC(stack *node.Node) error {
config := comms.HttpConfig{
ListenAddress: "127.0.0.1",
ListenPort: 8545,
}
xeth := xeth.New(stack, nil)
codec := codec.JSON
/*
web3 := NewPublicWeb3API(stack)
server.RegisterName("web3", web3)
net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
server.RegisterName("net", net)
*/
apis, err := api.ParseApiString(comms.DefaultHttpRpcApis, codec, xeth, stack)
if err != nil {
return err
for _, api := range stack.APIs() {
if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok {
_, err := adminApi.StartRPC("127.0.0.1", 8545, "", "admin,db,eth,debug,miner,net,shh,txpool,personal,web3")
return err
}
}
return comms.StartHttp(config, codec, api.Merge(apis...))
glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API")
return errors.New("Unable to start RPC-HTTP interface")
}
// StartRPC initializes an IPC interface to the given protocol stack.
// StartIPC initializes an IPC interface to the given protocol stack.
func StartIPC(stack *node.Node) error {
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil {
@ -202,11 +203,7 @@ func StartIPC(stack *node.Node) error {
endpoint = filepath.Join(common.DefaultDataDir(), "geth.ipc")
}
config := comms.IpcConfig{
Endpoint: endpoint,
}
listener, err := comms.CreateListener(config)
listener, err := rpc.CreateIPCListener(endpoint)
if err != nil {
return err
}
@ -217,16 +214,16 @@ func StartIPC(stack *node.Node) error {
offered := stack.APIs()
for _, api := range offered {
server.RegisterName(api.Namespace, api.Service)
glog.V(logger.Debug).Infof("Register %T@%s for IPC service\n", api.Service, api.Namespace)
glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace)
}
web3 := utils.NewPublicWeb3API(stack)
server.RegisterName("web3", web3)
net := utils.NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
server.RegisterName("net", net)
//var ethereum *eth.Ethereum
//if err := stack.Service(&ethereum); err != nil {
// return err
//}
go func() {
glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint)
glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint)
for {
conn, err := listener.Accept()
if err != nil {

View File

@ -1,74 +0,0 @@
// Copyright 2015 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 utils
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
)
// PublicWeb3API offers helper utils
type PublicWeb3API struct {
stack *node.Node
}
// NewPublicWeb3API creates a new Web3Service instance
func NewPublicWeb3API(stack *node.Node) *PublicWeb3API {
return &PublicWeb3API{stack}
}
// ClientVersion returns the node name
func (s *PublicWeb3API) ClientVersion() string {
return s.stack.Server().Name
}
// Sha3 applies the ethereum sha3 implementation on the input.
// It assumes the input is hex encoded.
func (s *PublicWeb3API) Sha3(input string) string {
return common.ToHex(crypto.Sha3(common.FromHex(input)))
}
// PublicNetAPI offers network related RPC methods
type PublicNetAPI struct {
net *p2p.Server
networkVersion int
}
// NewPublicNetAPI creates a new net api instance.
func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI {
return &PublicNetAPI{net, networkVersion}
}
// Listening returns an indication if the node is listening for network connections.
func (s *PublicNetAPI) Listening() bool {
return true // always listening
}
// Peercount returns the number of connected peers
func (s *PublicNetAPI) PeerCount() *rpc.HexNumber {
return rpc.NewHexNumber(s.net.PeerCount())
}
// ProtocolVersion returns the current ethereum protocol version.
func (s *PublicNetAPI) Version() string {
return fmt.Sprintf("%d", s.networkVersion)
}

176
cmd/utils/client.go Normal file
View File

@ -0,0 +1,176 @@
// Copyright 2015 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 utils
import (
"encoding/json"
"fmt"
"strings"
"github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
)
// NewInProcRPCClient will start a new RPC server for the given node and returns a client to interact with it.
func NewInProcRPCClient(stack *node.Node) *inProcClient {
server := rpc.NewServer()
offered := stack.APIs()
for _, api := range offered {
server.RegisterName(api.Namespace, api.Service)
}
web3 := node.NewPublicWeb3API(stack)
server.RegisterName("web3", web3)
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err == nil {
net := eth.NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
server.RegisterName("net", net)
} else {
glog.V(logger.Warn).Infof("%v\n", err)
}
buf := &buf{
requests: make(chan []byte),
responses: make(chan []byte),
}
client := &inProcClient{
server: server,
buf: buf,
}
go func() {
server.ServeCodec(rpc.NewJSONCodec(client.buf))
}()
return client
}
// buf represents the connection between the RPC server and console
type buf struct {
readBuf []byte // store remaining request bytes after a partial read
requests chan []byte // list with raw serialized requests
responses chan []byte // list with raw serialized responses
}
// will read the next request in json format
func (b *buf) Read(p []byte) (int, error) {
// last read didn't read entire request, return remaining bytes
if len(b.readBuf) > 0 {
n := copy(p, b.readBuf)
if n < len(b.readBuf) {
b.readBuf = b.readBuf[:n]
} else {
b.readBuf = b.readBuf[:0]
}
return n, nil
}
// read next request
req := <-b.requests
n := copy(p, req)
if n < len(req) {
// buf too small, store remaining chunk for next read
b.readBuf = req[n:]
}
return n, nil
}
// Write send the given buffer to the backend
func (b *buf) Write(p []byte) (n int, err error) {
b.responses <- p
return len(p), nil
}
// Close cleans up obtained resources.
func (b *buf) Close() error {
close(b.requests)
close(b.responses)
return nil
}
// inProcClient starts a RPC server and uses buf to communicate with it.
type inProcClient struct {
server *rpc.Server
buf *buf
}
// Close will stop the RPC server
func (c *inProcClient) Close() {
c.server.Stop()
}
// Send a msg to the endpoint
func (c *inProcClient) Send(msg interface{}) error {
d, err := json.Marshal(msg)
if err != nil {
return err
}
c.buf.requests <- d
return nil
}
// Recv reads a message and tries to parse it into the given msg
func (c *inProcClient) Recv(msg interface{}) error {
data := <-c.buf.responses
return json.Unmarshal(data, &msg)
}
// Returns the collection of modules the RPC server offers.
func (c *inProcClient) SupportedModules() (map[string]string, error) {
return rpc.SupportedModules(c)
}
// NewRemoteRPCClient returns a RPC client which connects to a running geth instance.
// Depending on the given context this can either be a IPC or a HTTP client.
func NewRemoteRPCClient(ctx *cli.Context) (rpc.Client, error) {
if ctx.Args().Present() {
endpoint := ctx.Args().First()
return NewRemoteRPCClientFromString(endpoint)
}
// use IPC by default
endpoint := IPCSocketPath(ctx)
return rpc.NewIPCClient(endpoint)
}
// NewRemoteRPCClientFromString returns a RPC client which connects to the given
// endpoint. It must start with either `ipc:` or `rpc:` (HTTP).
func NewRemoteRPCClientFromString(endpoint string) (rpc.Client, error) {
if strings.HasPrefix(endpoint, "ipc:") {
return rpc.NewIPCClient(endpoint[4:])
}
if strings.HasPrefix(endpoint, "rpc:") {
return rpc.NewHTTPClient(endpoint[4:])
}
if strings.HasPrefix(endpoint, "http://") {
return rpc.NewHTTPClient(endpoint)
}
if strings.HasPrefix(endpoint, "ws:") {
return rpc.NewWSClient(endpoint)
}
return nil, fmt.Errorf("invalid endpoint")
}

View File

@ -23,7 +23,6 @@ import (
"log"
"math"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
@ -31,6 +30,8 @@ import (
"strconv"
"strings"
"errors"
"github.com/codegangsta/cli"
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
@ -49,14 +50,8 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc/api"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/comms"
"github.com/ethereum/go-ethereum/rpc/shared"
"github.com/ethereum/go-ethereum/rpc/useragent"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/whisper"
"github.com/ethereum/go-ethereum/xeth"
)
func init() {
@ -282,10 +277,10 @@ var (
Usage: "Domains from which to accept cross origin requests (browser enforced)",
Value: "",
}
RpcApiFlag = cli.StringFlag{
RPCApiFlag = cli.StringFlag{
Name: "rpcapi",
Usage: "API's offered over the HTTP-RPC interface",
Value: comms.DefaultHttpRpcApis,
Value: rpc.DefaultHttpRpcApis,
}
IPCDisabledFlag = cli.BoolFlag{
Name: "ipcdisable",
@ -294,16 +289,36 @@ var (
IPCApiFlag = cli.StringFlag{
Name: "ipcapi",
Usage: "API's offered over the IPC-RPC interface",
Value: comms.DefaultIpcApis,
Value: rpc.DefaultIpcApis,
}
IPCPathFlag = DirectoryFlag{
Name: "ipcpath",
Usage: "Filename for IPC socket/pipe",
Value: DirectoryString{common.DefaultIpcPath()},
}
IPCExperimental = cli.BoolFlag{
Name: "ipcexp",
Usage: "Enable the new RPC implementation",
WSEnabledFlag = cli.BoolFlag{
Name: "ws",
Usage: "Enable the WS-RPC server",
}
WSListenAddrFlag = cli.StringFlag{
Name: "wsaddr",
Usage: "WS-RPC server listening interface",
Value: "127.0.0.1",
}
WSPortFlag = cli.IntFlag{
Name: "wsport",
Usage: "WS-RPC server listening port",
Value: 8546,
}
WSApiFlag = cli.StringFlag{
Name: "wsapi",
Usage: "API's offered over the WS-RPC interface",
Value: rpc.DefaultHttpRpcApis,
}
WSAllowedDomainsFlag = cli.StringFlag{
Name: "wsdomains",
Usage: "Domains from which to accept websockets requests",
Value: "",
}
ExecFlag = cli.StringFlag{
Name: "exec",
@ -760,7 +775,7 @@ func MakeChain(ctx *cli.Context) (chain *core.BlockChain, chainDb ethdb.Database
return chain, chainDb
}
func IpcSocketPath(ctx *cli.Context) (ipcpath string) {
func IPCSocketPath(ctx *cli.Context) (ipcpath string) {
if runtime.GOOS == "windows" {
ipcpath = common.DefaultIpcPath()
if ctx.GlobalIsSet(IPCPathFlag.Name) {
@ -780,79 +795,83 @@ func IpcSocketPath(ctx *cli.Context) (ipcpath string) {
}
func StartIPC(stack *node.Node, ctx *cli.Context) error {
config := comms.IpcConfig{
Endpoint: IpcSocketPath(ctx),
}
var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil {
return err
}
if ctx.GlobalIsSet(IPCExperimental.Name) {
listener, err := comms.CreateListener(config)
if err != nil {
return err
}
endpoint := IPCSocketPath(ctx)
listener, err := rpc.CreateIPCListener(endpoint)
if err != nil {
return err
}
server := rpc.NewServer()
server := rpc.NewServer()
// register package API's this node provides
offered := stack.APIs()
for _, api := range offered {
server.RegisterName(api.Namespace, api.Service)
glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace)
}
// register package API's this node provides
offered := stack.APIs()
for _, api := range offered {
server.RegisterName(api.Namespace, api.Service)
glog.V(logger.Debug).Infof("Register %T under namespace '%s' for IPC service\n", api.Service, api.Namespace)
}
web3 := NewPublicWeb3API(stack)
server.RegisterName("web3", web3)
net := NewPublicNetAPI(stack.Server(), ethereum.NetVersion())
server.RegisterName("net", net)
go func() {
glog.V(logger.Info).Infof("Start IPC server on %s\n", config.Endpoint)
for {
conn, err := listener.Accept()
if err != nil {
glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err)
}
codec := rpc.NewJSONCodec(conn)
go server.ServeCodec(codec)
go func() {
glog.V(logger.Info).Infof("Start IPC server on %s\n", endpoint)
for {
conn, err := listener.Accept()
if err != nil {
glog.V(logger.Error).Infof("Unable to accept connection - %v\n", err)
}
}()
return nil
}
initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) {
fe := useragent.NewRemoteFrontend(conn, ethereum.AccountManager())
xeth := xeth.New(stack, fe)
apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, stack)
if err != nil {
return nil, nil, err
codec := rpc.NewJSONCodec(conn)
go server.ServeCodec(codec)
}
return xeth, api.Merge(apis...), nil
}
return comms.StartIpc(config, codec.JSON, initializer)
}()
return nil
}
// StartRPC starts a HTTP JSON-RPC API server.
func StartRPC(stack *node.Node, ctx *cli.Context) error {
config := comms.HttpConfig{
ListenAddress: ctx.GlobalString(RPCListenAddrFlag.Name),
ListenPort: uint(ctx.GlobalInt(RPCPortFlag.Name)),
CorsDomain: ctx.GlobalString(RPCCORSDomainFlag.Name),
for _, api := range stack.APIs() {
if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok {
address := ctx.GlobalString(RPCListenAddrFlag.Name)
port := ctx.GlobalInt(RPCPortFlag.Name)
cors := ctx.GlobalString(RPCCORSDomainFlag.Name)
apiStr := ""
if ctx.GlobalIsSet(RPCApiFlag.Name) {
apiStr = ctx.GlobalString(RPCApiFlag.Name)
}
_, err := adminApi.StartRPC(address, port, cors, apiStr)
return err
}
}
xeth := xeth.New(stack, nil)
codec := codec.JSON
glog.V(logger.Error).Infof("Unable to start RPC-HTTP interface, could not find admin API")
return errors.New("Unable to start RPC-HTTP interface")
}
apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, stack)
if err != nil {
return err
// StartWS starts a websocket JSON-RPC API server.
func StartWS(stack *node.Node, ctx *cli.Context) error {
for _, api := range stack.APIs() {
if adminApi, ok := api.Service.(*node.PrivateAdminAPI); ok {
address := ctx.GlobalString(WSListenAddrFlag.Name)
port := ctx.GlobalInt(WSAllowedDomainsFlag.Name)
allowedDomains := ctx.GlobalString(WSAllowedDomainsFlag.Name)
apiStr := ""
if ctx.GlobalIsSet(WSApiFlag.Name) {
apiStr = ctx.GlobalString(WSApiFlag.Name)
}
_, err := adminApi.StartWS(address, port, allowedDomains, apiStr)
return err
}
}
return comms.StartHttp(config, codec, api.Merge(apis...))
glog.V(logger.Error).Infof("Unable to start RPC-WS interface, could not find admin API")
return errors.New("Unable to start RPC-WS interface")
}
func StartPProf(ctx *cli.Context) {

323
cmd/utils/jeth.go Normal file
View File

@ -0,0 +1,323 @@
// Copyright 2015 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 utils
import (
"encoding/json"
"fmt"
"time"
"github.com/ethereum/go-ethereum/jsre"
"github.com/ethereum/go-ethereum/rpc"
"github.com/robertkrimen/otto"
)
type Jeth struct {
re *jsre.JSRE
client rpc.Client
}
// NewJeth create a new backend for the JSRE console
func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth {
return &Jeth{re, client}
}
func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (response otto.Value) {
m := rpc.JSONErrResponse{
Version: "2.0",
Id: id,
Error: rpc.JSONError{
Code: code,
Message: msg,
},
}
errObj, _ := json.Marshal(m.Error)
errRes, _ := json.Marshal(m)
call.Otto.Run("ret_error = " + string(errObj))
res, _ := call.Otto.Run("ret_response = " + string(errRes))
return res
}
// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre
func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) {
var cmd, account, passwd string
timeout := int64(300)
var ok bool
if len(call.ArgumentList) == 0 {
fmt.Println("expected address of account to unlock")
return otto.FalseValue()
}
if len(call.ArgumentList) >= 1 {
if accountExport, err := call.Argument(0).Export(); err == nil {
if account, ok = accountExport.(string); ok {
if len(call.ArgumentList) == 1 {
fmt.Printf("Unlock account %s\n", account)
passwd, err = PromptPassword("Passphrase: ", true)
if err != nil {
return otto.FalseValue()
}
}
}
}
}
if len(call.ArgumentList) >= 2 {
if passwdExport, err := call.Argument(1).Export(); err == nil {
passwd, _ = passwdExport.(string)
}
}
if len(call.ArgumentList) >= 3 {
if timeoutExport, err := call.Argument(2).Export(); err == nil {
timeout, _ = timeoutExport.(int64)
}
}
cmd = fmt.Sprintf("jeth.unlockAccount('%s', '%s', %d)", account, passwd, timeout)
if val, err := call.Otto.Run(cmd); err == nil {
return val
}
return otto.FalseValue()
}
// NewAccount asks the user for the password and than executes the jeth.newAccount callback in the jsre
func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) {
if len(call.ArgumentList) == 0 {
passwd, err := PromptPassword("Passphrase: ", true)
if err != nil {
return otto.FalseValue()
}
passwd2, err := PromptPassword("Repeat passphrase: ", true)
if err != nil {
return otto.FalseValue()
}
if passwd != passwd2 {
fmt.Println("Passphrases don't match")
return otto.FalseValue()
}
cmd := fmt.Sprintf("jeth.newAccount('%s')", passwd)
if val, err := call.Otto.Run(cmd); err == nil {
return val
}
} else {
fmt.Println("New account doesn't expect argument(s), you will be prompted for a password")
}
return otto.FalseValue()
}
func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) {
reqif, err := call.Argument(0).Export()
if err != nil {
return self.err(call, -32700, err.Error(), nil)
}
jsonreq, err := json.Marshal(reqif)
var reqs []rpc.JSONRequest
batch := true
err = json.Unmarshal(jsonreq, &reqs)
if err != nil {
reqs = make([]rpc.JSONRequest, 1)
err = json.Unmarshal(jsonreq, &reqs[0])
batch = false
}
call.Otto.Set("response_len", len(reqs))
call.Otto.Run("var ret_response = new Array(response_len);")
for i, req := range reqs {
err := self.client.Send(&req)
if err != nil {
return self.err(call, -32603, err.Error(), req.Id)
}
result := make(map[string]interface{})
err = self.client.Recv(&result)
if err != nil {
return self.err(call, -32603, err.Error(), req.Id)
}
_, isSuccessResponse := result["result"]
_, isErrorResponse := result["error"]
if !isSuccessResponse && !isErrorResponse {
return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64))
}
id, _ := result["id"]
call.Otto.Set("ret_id", id)
jsonver, _ := result["jsonrpc"]
call.Otto.Set("ret_jsonrpc", jsonver)
var payload []byte
if isSuccessResponse {
payload, _ = json.Marshal(result["result"])
} else if isErrorResponse {
payload, _ = json.Marshal(result["error"])
}
call.Otto.Set("ret_result", string(payload))
call.Otto.Set("response_idx", i)
response, err = call.Otto.Run(`
ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) };
`)
}
if !batch {
call.Otto.Run("ret_response = ret_response[0];")
}
if call.Argument(1).IsObject() {
call.Otto.Set("callback", call.Argument(1))
call.Otto.Run(`
if (Object.prototype.toString.call(callback) == '[object Function]') {
callback(null, ret_response);
}
`)
}
return
}
/*
// handleRequest will handle user agent requests by interacting with the user and sending
// the user response back to the geth service
func (self *Jeth) handleRequest(req *shared.Request) bool {
var err error
var args []interface{}
if err = json.Unmarshal(req.Params, &args); err != nil {
glog.V(logger.Info).Infof("Unable to parse agent request - %v\n", err)
return false
}
switch req.Method {
case useragent.AskPasswordMethod:
return self.askPassword(req.Id, req.Jsonrpc, args)
case useragent.ConfirmTransactionMethod:
return self.confirmTransaction(req.Id, req.Jsonrpc, args)
}
return false
}
// askPassword will ask the user to supply the password for a given account
func (self *Jeth) askPassword(id interface{}, jsonrpc string, args []interface{}) bool {
var err error
var passwd string
if len(args) >= 1 {
if account, ok := args[0].(string); ok {
fmt.Printf("Unlock account %s\n", account)
} else {
return false
}
}
passwd, err = PromptPassword("Passphrase: ", true)
if err = self.client.Send(shared.NewRpcResponse(id, jsonrpc, passwd, err)); err != nil {
glog.V(logger.Info).Infof("Unable to send user agent ask password response - %v\n", err)
}
return err == nil
}
func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []interface{}) bool {
// Accept all tx which are send from this console
return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil
}
*/
// throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error.
func throwJSExeception(msg interface{}) otto.Value {
p, _ := otto.ToValue(msg)
panic(p)
return p
}
// Sleep will halt the console for arg[0] seconds.
func (self *Jeth) Sleep(call otto.FunctionCall) (response otto.Value) {
if len(call.ArgumentList) >= 1 {
if call.Argument(0).IsNumber() {
sleep, _ := call.Argument(0).ToInteger()
time.Sleep(time.Duration(sleep) * time.Second)
return otto.TrueValue()
}
}
return throwJSExeception("usage: sleep(<sleep in seconds>)")
}
// SleepBlocks will wait for a specified number of new blocks or max for a
// given of seconds. sleepBlocks(nBlocks[, maxSleep]).
func (self *Jeth) SleepBlocks(call otto.FunctionCall) (response otto.Value) {
nBlocks := int64(0)
maxSleep := int64(9999999999999999) // indefinitely
nArgs := len(call.ArgumentList)
if nArgs == 0 {
throwJSExeception("usage: sleepBlocks(<n blocks>[, max sleep in seconds])")
}
if nArgs >= 1 {
if call.Argument(0).IsNumber() {
nBlocks, _ = call.Argument(0).ToInteger()
} else {
throwJSExeception("expected number as first argument")
}
}
if nArgs >= 2 {
if call.Argument(1).IsNumber() {
maxSleep, _ = call.Argument(1).ToInteger()
} else {
throwJSExeception("expected number as second argument")
}
}
// go through the console, this will allow web3 to call the appropriate
// callbacks if a delayed response or notification is received.
currentBlockNr := func() int64 {
result, err := call.Otto.Run("eth.blockNumber")
if err != nil {
throwJSExeception(err.Error())
}
blockNr, err := result.ToInteger()
if err != nil {
throwJSExeception(err.Error())
}
return blockNr
}
targetBlockNr := currentBlockNr() + nBlocks
deadline := time.Now().Add(time.Duration(maxSleep) * time.Second)
for time.Now().Before(deadline) {
if currentBlockNr() >= targetBlockNr {
return otto.TrueValue()
}
time.Sleep(time.Second)
}
return otto.FalseValue()
}