From d8370a4e15f00afeb783f7f3be8b47e93c4338d2 Mon Sep 17 00:00:00 2001 From: Péter Szilágyi Date: Fri, 4 Dec 2015 20:56:11 +0200 Subject: core, eth, node, rpc: port the admin and debug API --- cmd/gethrpctest/main.go | 2 +- cmd/utils/flags.go | 2 +- eth/api.go | 217 +++++++++++++++++++++++++++++++++++++++--- eth/backend.go | 13 +++ node/api.go | 244 ++++++++++++++++++++++++++++++++++++++++++++++++ node/node.go | 30 +++++- rpc/api/debug_js.go | 12 +-- 7 files changed, 494 insertions(+), 26 deletions(-) create mode 100644 node/api.go diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index 636d329e4..ae815c4a6 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -214,7 +214,7 @@ func StartIPC(stack *node.Node) error { server := rpc.NewServer() // register package API's this node provides - offered := stack.RPCAPIs() + 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) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 1aa8f4e89..63efa08ee 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -798,7 +798,7 @@ func StartIPC(stack *node.Node, ctx *cli.Context) error { server := rpc.NewServer() // register package API's this node provides - offered := stack.RPCAPIs() + 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) diff --git a/eth/api.go b/eth/api.go index 06fc2deb1..068b350db 100644 --- a/eth/api.go +++ b/eth/api.go @@ -21,7 +21,9 @@ import ( "encoding/json" "errors" "fmt" + "io" "math/big" + "os" "sync" "time" @@ -46,7 +48,7 @@ import ( const ( defaultGasPrice = uint64(10000000000000) - defaultGas = uint64(90000) + defaultGas = uint64(90000) ) // PublicEthereumAPI provides an API to access Ethereum related information. @@ -471,12 +473,12 @@ type callmsg struct { // accessor boilerplate to implement core.Message func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) Nonce() uint64 { return m.from.Nonce() } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gas } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } +func (m callmsg) Nonce() uint64 { return m.from.Nonce() } +func (m callmsg) To() *common.Address { return m.to } +func (m callmsg) GasPrice() *big.Int { return m.gasPrice } +func (m callmsg) Gas() *big.Int { return m.gas } +func (m callmsg) Value() *big.Int { return m.value } +func (m callmsg) Data() []byte { return m.data } type CallArgs struct { From common.Address `json:"from"` @@ -972,20 +974,20 @@ func (s *PublicTransactionPoolAPI) Sign(address common.Address, data string) (st } type SignTransactionArgs struct { - From common.Address - To common.Address - Nonce *rpc.HexNumber - Value *rpc.HexNumber - Gas *rpc.HexNumber - GasPrice *rpc.HexNumber - Data string + From common.Address + To common.Address + Nonce *rpc.HexNumber + Value *rpc.HexNumber + Gas *rpc.HexNumber + GasPrice *rpc.HexNumber + Data string BlockNumber int64 } // Tx is a helper object for argument and return values type Tx struct { - tx *types.Transaction + tx *types.Transaction To *common.Address `json:"to"` From common.Address `json:"from"` @@ -1214,3 +1216,188 @@ func (s *PublicTransactionPoolAPI) Resend(tx *Tx, gasPrice, gasLimit *rpc.HexNum return common.Hash{}, fmt.Errorf("Transaction %#x not found", tx.Hash) } + +// PrivateAdminAPI is the collection of Etheruem APIs exposed over the private +// admin endpoint. +type PrivateAdminAPI struct { + eth *Ethereum +} + +// NewPrivateAdminAPI creates a new API definition for the private admin methods +// of the Ethereum service. +func NewPrivateAdminAPI(eth *Ethereum) *PrivateAdminAPI { + return &PrivateAdminAPI{eth: eth} +} + +// SetSolc sets the Solidity compiler path to be used by the node. +func (api *PrivateAdminAPI) SetSolc(path string) (string, error) { + solc, err := api.eth.SetSolc(path) + if err != nil { + return "", err + } + return solc.Info(), nil +} + +// ExportChain exports the current blockchain into a local file. +func (api *PrivateAdminAPI) ExportChain(file string) (bool, error) { + // Make sure we can create the file to export into + out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return false, err + } + defer out.Close() + + // Export the blockchain + if err := api.eth.BlockChain().Export(out); err != nil { + return false, err + } + return true, nil +} + +// ImportChain imports a blockchain from a local file. +func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { + // Make sure the can access the file to import + in, err := os.Open(file) + if err != nil { + return false, err + } + defer in.Close() + + // Run actual the import in pre-configured batches + stream := rlp.NewStream(in, 0) + + blocks, index := make([]*types.Block, 0, 2500), 0 + for batch := 0; ; batch++ { + // Load a batch of blocks from the input file + for len(blocks) < cap(blocks) { + block := new(types.Block) + if err := stream.Decode(block); err == io.EOF { + break + } else if err != nil { + return false, fmt.Errorf("block %d: failed to parse: %v", index, err) + } + blocks = append(blocks, block) + index++ + } + if len(blocks) == 0 { + break + } + // Import the batch and reset the buffer + if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { + return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) + } + blocks = blocks[:0] + } + return true, nil +} + +// PublicDebugAPI is the collection of Etheruem APIs exposed over the public +// debugging endpoint. +type PublicDebugAPI struct { + eth *Ethereum +} + +// NewPublicDebugAPI creates a new API definition for the public debug methods +// of the Ethereum service. +func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI { + return &PublicDebugAPI{eth: eth} +} + +// DumpBlock retrieves the entire state of the database at a given block. +func (api *PublicDebugAPI) DumpBlock(number uint64) (state.World, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return state.World{}, fmt.Errorf("block #%d not found", number) + } + stateDb, err := state.New(block.Root(), api.eth.ChainDb()) + if err != nil { + return state.World{}, err + } + return stateDb.RawDump(), nil +} + +// GetBlockRlp retrieves the RLP encoded for of a single block. +func (api *PublicDebugAPI) GetBlockRlp(number uint64) (string, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return "", fmt.Errorf("block #%d not found", number) + } + encoded, err := rlp.EncodeToBytes(block) + if err != nil { + return "", err + } + return fmt.Sprintf("%x", encoded), nil +} + +// PrintBlock retrieves a block and returns its pretty printed form. +func (api *PublicDebugAPI) PrintBlock(number uint64) (string, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return "", fmt.Errorf("block #%d not found", number) + } + return fmt.Sprintf("%s", block), nil +} + +// SeedHash retrieves the seed hash of a block. +func (api *PublicDebugAPI) SeedHash(number uint64) (string, error) { + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return "", fmt.Errorf("block #%d not found", number) + } + hash, err := ethash.GetSeedHash(number) + if err != nil { + return "", err + } + return fmt.Sprintf("0x%x", hash), nil +} + +// PrivateDebugAPI is the collection of Etheruem APIs exposed over the private +// debugging endpoint. +type PrivateDebugAPI struct { + eth *Ethereum +} + +// NewPrivateDebugAPI creates a new API definition for the private debug methods +// of the Ethereum service. +func NewPrivateDebugAPI(eth *Ethereum) *PrivateDebugAPI { + return &PrivateDebugAPI{eth: eth} +} + +// ProcessBlock reprocesses an already owned block. +func (api *PrivateDebugAPI) ProcessBlock(number uint64) (bool, error) { + // Fetch the block that we aim to reprocess + block := api.eth.BlockChain().GetBlockByNumber(number) + if block == nil { + return false, fmt.Errorf("block #%d not found", number) + } + // Temporarily enable debugging + defer func(old bool) { vm.Debug = old }(vm.Debug) + vm.Debug = true + + // Validate and reprocess the block + var ( + blockchain = api.eth.BlockChain() + validator = blockchain.Validator() + processor = blockchain.Processor() + ) + if err := core.ValidateHeader(blockchain.AuxValidator(), block.Header(), blockchain.GetHeader(block.ParentHash()), true, false); err != nil { + return false, err + } + statedb, err := state.New(blockchain.GetBlock(block.ParentHash()).Root(), api.eth.ChainDb()) + if err != nil { + return false, err + } + receipts, _, usedGas, err := processor.Process(block, statedb) + if err != nil { + return false, err + } + if err := validator.ValidateState(block, blockchain.GetBlock(block.ParentHash()), statedb, receipts, usedGas); err != nil { + return false, err + } + return true, nil +} + +// SetHead rewinds the head of the blockchain to a previous block. +func (api *PrivateDebugAPI) SetHead(number uint64) { + api.eth.BlockChain().SetHead(number) +} diff --git a/eth/backend.go b/eth/backend.go index ad98635a5..d51446d51 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -295,6 +295,19 @@ func (s *Ethereum) APIs() []rpc.API { Version: "1.0", Service: filters.NewPublicFilterAPI(s.ChainDb(), s.EventMux()), Public: true, + }, { + Namespace: "admin", + Version: "1.0", + Service: NewPrivateAdminAPI(s), + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPublicDebugAPI(s), + Public: true, + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPrivateDebugAPI(s), }, } } diff --git a/node/api.go b/node/api.go new file mode 100644 index 000000000..a44ee16c0 --- /dev/null +++ b/node/api.go @@ -0,0 +1,244 @@ +// 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 . + +package node + +import ( + "fmt" + "strings" + "time" + + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/rpc/comms" + "github.com/rcrowley/go-metrics" +) + +// PrivateAdminAPI is the collection of administrative API methods exposed only +// over a secure RPC channel. +type PrivateAdminAPI struct { + node *Node // Node interfaced by this API +} + +// NewPrivateAdminAPI creates a new API definition for the private admin methods +// of the node itself. +func NewPrivateAdminAPI(node *Node) *PrivateAdminAPI { + return &PrivateAdminAPI{node: node} +} + +// AddPeer requests connecting to a remote node, and also maintaining the new +// connection at all times, even reconnecting if it is lost. +func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { + // Make sure the server is running, fail otherwise + server := api.node.Server() + if server == nil { + return false, ErrNodeStopped + } + // Try to add the url as a static peer and return + node, err := discover.ParseNode(url) + if err != nil { + return false, fmt.Errorf("invalid enode: %v", err) + } + server.AddPeer(node) + return true, nil +} + +// StartRPC starts the HTTP RPC API server. +func (api *PrivateAdminAPI) StartRPC(address string, port int, cors string, apis string) (bool, error) { + /*// Parse the list of API modules to make available + apis, err := api.ParseApiString(apis, codec.JSON, xeth.New(api.node, nil), api.node) + if err != nil { + return false, err + } + // Configure and start the HTTP RPC server + config := comms.HttpConfig{ + ListenAddress: address, + ListenPort: port, + CorsDomain: cors, + } + if err := comms.StartHttp(config, self.codec, api.Merge(apis...)); err != nil { + return false, err + } + return true, nil*/ + return false, fmt.Errorf("needs new RPC implementation to resolve circular dependency") +} + +// StopRPC terminates an already running HTTP RPC API endpoint. +func (api *PrivateAdminAPI) StopRPC() { + comms.StopHttp() +} + +// PublicAdminAPI is the collection of administrative API methods exposed over +// both secure and unsecure RPC channels. +type PublicAdminAPI struct { + node *Node // Node interfaced by this API +} + +// NewPublicAdminAPI creates a new API definition for the public admin methods +// of the node itself. +func NewPublicAdminAPI(node *Node) *PublicAdminAPI { + return &PublicAdminAPI{node: node} +} + +// Peers retrieves all the information we know about each individual peer at the +// protocol granularity. +func (api *PublicAdminAPI) Peers() ([]*p2p.PeerInfo, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.PeersInfo(), nil +} + +// NodeInfo retrieves all the information we know about the host node at the +// protocol granularity. +func (api *PublicAdminAPI) NodeInfo() (*p2p.NodeInfo, error) { + server := api.node.Server() + if server == nil { + return nil, ErrNodeStopped + } + return server.NodeInfo(), nil +} + +// Datadir retrieves the current data directory the node is using. +func (api *PublicAdminAPI) Datadir() string { + return api.node.DataDir() +} + +// PrivateDebugAPI is the collection of debugging related API methods exposed +// only over a secure RPC channel. +type PrivateDebugAPI struct { + node *Node // Node interfaced by this API +} + +// NewPrivateDebugAPI creates a new API definition for the private debug methods +// of the node itself. +func NewPrivateDebugAPI(node *Node) *PrivateDebugAPI { + return &PrivateDebugAPI{node: node} +} + +// Verbosity updates the node's logging verbosity. Note, due to the lack of fine +// grained contextual loggers, this will update the verbosity level for the entire +// process, not just this node instance. +func (api *PrivateDebugAPI) Verbosity(level int) { + glog.SetV(level) +} + +// PublicDebugAPI is the collection of debugging related API methods exposed over +// both secure and unsecure RPC channels. +type PublicDebugAPI struct { + node *Node // Node interfaced by this API +} + +// NewPublicDebugAPI creates a new API definition for the public debug methods +// of the node itself. +func NewPublicDebugAPI(node *Node) *PublicDebugAPI { + return &PublicDebugAPI{node: node} +} + +// Metrics retrieves all the known system metric collected by the node. +func (api *PublicDebugAPI) Metrics(raw bool) (map[string]interface{}, error) { + // Create a rate formatter + units := []string{"", "K", "M", "G", "T", "E", "P"} + round := func(value float64, prec int) string { + unit := 0 + for value >= 1000 { + unit, value, prec = unit+1, value/1000, 2 + } + return fmt.Sprintf(fmt.Sprintf("%%.%df%s", prec, units[unit]), value) + } + format := func(total float64, rate float64) string { + return fmt.Sprintf("%s (%s/s)", round(total, 0), round(rate, 2)) + } + // Iterate over all the metrics, and just dump for now + counters := make(map[string]interface{}) + metrics.DefaultRegistry.Each(func(name string, metric interface{}) { + // Create or retrieve the counter hierarchy for this metric + root, parts := counters, strings.Split(name, "/") + for _, part := range parts[:len(parts)-1] { + if _, ok := root[part]; !ok { + root[part] = make(map[string]interface{}) + } + root = root[part].(map[string]interface{}) + } + name = parts[len(parts)-1] + + // Fill the counter with the metric details, formatting if requested + if raw { + switch metric := metric.(type) { + case metrics.Meter: + root[name] = map[string]interface{}{ + "AvgRate01Min": metric.Rate1(), + "AvgRate05Min": metric.Rate5(), + "AvgRate15Min": metric.Rate15(), + "MeanRate": metric.RateMean(), + "Overall": float64(metric.Count()), + } + + case metrics.Timer: + root[name] = map[string]interface{}{ + "AvgRate01Min": metric.Rate1(), + "AvgRate05Min": metric.Rate5(), + "AvgRate15Min": metric.Rate15(), + "MeanRate": metric.RateMean(), + "Overall": float64(metric.Count()), + "Percentiles": map[string]interface{}{ + "5": metric.Percentile(0.05), + "20": metric.Percentile(0.2), + "50": metric.Percentile(0.5), + "80": metric.Percentile(0.8), + "95": metric.Percentile(0.95), + }, + } + + default: + root[name] = "Unknown metric type" + } + } else { + switch metric := metric.(type) { + case metrics.Meter: + root[name] = map[string]interface{}{ + "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), + "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), + "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), + "Overall": format(float64(metric.Count()), metric.RateMean()), + } + + case metrics.Timer: + root[name] = map[string]interface{}{ + "Avg01Min": format(metric.Rate1()*60, metric.Rate1()), + "Avg05Min": format(metric.Rate5()*300, metric.Rate5()), + "Avg15Min": format(metric.Rate15()*900, metric.Rate15()), + "Overall": format(float64(metric.Count()), metric.RateMean()), + "Maximum": time.Duration(metric.Max()).String(), + "Minimum": time.Duration(metric.Min()).String(), + "Percentiles": map[string]interface{}{ + "5": time.Duration(metric.Percentile(0.05)).String(), + "20": time.Duration(metric.Percentile(0.2)).String(), + "50": time.Duration(metric.Percentile(0.5)).String(), + "80": time.Duration(metric.Percentile(0.8)).String(), + "95": time.Duration(metric.Percentile(0.95)).String(), + }, + } + + default: + root[name] = "Unknown metric type" + } + } + }) + return counters, nil +} diff --git a/node/node.go b/node/node.go index d6debe123..5d7b5869c 100644 --- a/node/node.go +++ b/node/node.go @@ -266,9 +266,33 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } -// RPCAPIs returns the collection of RPC descriptor this node offers -func (n *Node) RPCAPIs() []rpc.API { - var apis []rpc.API +// APIs returns the collection of RPC descriptor this node offers. This method +// is just a quick placeholder passthrough for the RPC update, which in the next +// step will be fully integrated into the node itself. +func (n *Node) APIs() []rpc.API { + // Define all the APIs owned by the node itself + apis := []rpc.API{ + { + Namespace: "admin", + Version: "1.0", + Service: NewPrivateAdminAPI(n), + }, { + Namespace: "admin", + Version: "1.0", + Service: NewPublicAdminAPI(n), + Public: true, + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPrivateDebugAPI(n), + }, { + Namespace: "debug", + Version: "1.0", + Service: NewPublicDebugAPI(n), + Public: true, + }, + } + // Inject all the APIs owned by various services for _, api := range n.services { apis = append(apis, api.APIs()...) } diff --git a/rpc/api/debug_js.go b/rpc/api/debug_js.go index 0eb9f97f1..03755ada0 100644 --- a/rpc/api/debug_js.go +++ b/rpc/api/debug_js.go @@ -25,37 +25,37 @@ web3._extend({ name: 'printBlock', call: 'debug_printBlock', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'getBlockRlp', call: 'debug_getBlockRlp', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'setHead', call: 'debug_setHead', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'processBlock', call: 'debug_processBlock', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'seedHash', call: 'debug_seedHash', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'dumpBlock', call: 'debug_dumpBlock', params: 1, - inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] + inputFormatter: [null] }), new web3._extend.Method({ name: 'metrics', -- cgit