8b81ad1fc4
* lines with leading space are ommitted from history * exit processed even with whitespace around * all whitespace lines (not only empty ones) are ignored add 7 missing commands to admin api autocomplete registrar: methods now return proper error if reg addresses are not set. fixes #1457 rpc/console: fix personal.newAccount() regression. Now all comms accept interactive password registrar: add registrar tests for errors crypto: catch AES decryption error on presale wallet import + fix error msg format. fixes #1580 CLI: improve error message when starting a second instance of geth. fixes #1564 cli/accounts: unlock multiple accounts. fixes #1785 * make unlocking multiple accounts work with inline <() fd * passwdfile now correctly read only once * improve logs * fix CLI help text for unlocking fix regression with docRoot / admin API * docRoot/jspath passed to rpc/api ParseApis, which passes onto adminApi * docRoot field for JS console in order to pass when RPC is (re)started * improve flag desc for jspath common/docserver: catch http errors from response fix rpc/api tests common/natspec: fix end to end test (skipped because takes 8s) registrar: fix major regression: * deploy registrars on frontier * register HashsReg and UrlHint in GlobalRegistrar. * set all 3 contract addresses in code * zero out addresses first in tests
476 lines
13 KiB
Go
476 lines
13 KiB
Go
// 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 api
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/compiler"
|
|
"github.com/ethereum/go-ethereum/common/docserver"
|
|
"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/core/types"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/eth"
|
|
"github.com/ethereum/go-ethereum/logger/glog"
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
"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"
|
|
"github.com/ethereum/go-ethereum/xeth"
|
|
)
|
|
|
|
const (
|
|
AdminApiversion = "1.0"
|
|
importBatchSize = 2500
|
|
)
|
|
|
|
var (
|
|
// mapping between methods and handlers
|
|
AdminMapping = map[string]adminhandler{
|
|
"admin_addPeer": (*adminApi).AddPeer,
|
|
"admin_peers": (*adminApi).Peers,
|
|
"admin_nodeInfo": (*adminApi).NodeInfo,
|
|
"admin_exportChain": (*adminApi).ExportChain,
|
|
"admin_importChain": (*adminApi).ImportChain,
|
|
"admin_verbosity": (*adminApi).Verbosity,
|
|
"admin_setSolc": (*adminApi).SetSolc,
|
|
"admin_datadir": (*adminApi).DataDir,
|
|
"admin_startRPC": (*adminApi).StartRPC,
|
|
"admin_stopRPC": (*adminApi).StopRPC,
|
|
"admin_setGlobalRegistrar": (*adminApi).SetGlobalRegistrar,
|
|
"admin_setHashReg": (*adminApi).SetHashReg,
|
|
"admin_setUrlHint": (*adminApi).SetUrlHint,
|
|
"admin_saveInfo": (*adminApi).SaveInfo,
|
|
"admin_register": (*adminApi).Register,
|
|
"admin_registerUrl": (*adminApi).RegisterUrl,
|
|
"admin_startNatSpec": (*adminApi).StartNatSpec,
|
|
"admin_stopNatSpec": (*adminApi).StopNatSpec,
|
|
"admin_getContractInfo": (*adminApi).GetContractInfo,
|
|
"admin_httpGet": (*adminApi).HttpGet,
|
|
"admin_sleepBlocks": (*adminApi).SleepBlocks,
|
|
"admin_sleep": (*adminApi).Sleep,
|
|
"admin_enableUserAgent": (*adminApi).EnableUserAgent,
|
|
}
|
|
)
|
|
|
|
// admin callback handler
|
|
type adminhandler func(*adminApi, *shared.Request) (interface{}, error)
|
|
|
|
// admin api provider
|
|
type adminApi struct {
|
|
xeth *xeth.XEth
|
|
ethereum *eth.Ethereum
|
|
codec codec.Codec
|
|
coder codec.ApiCoder
|
|
docRoot string
|
|
ds *docserver.DocServer
|
|
}
|
|
|
|
// create a new admin api instance
|
|
func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, codec codec.Codec, docRoot string) *adminApi {
|
|
return &adminApi{
|
|
xeth: xeth,
|
|
ethereum: ethereum,
|
|
codec: codec,
|
|
coder: codec.New(nil),
|
|
docRoot: docRoot,
|
|
ds: docserver.New(docRoot),
|
|
}
|
|
}
|
|
|
|
// collection with supported methods
|
|
func (self *adminApi) Methods() []string {
|
|
methods := make([]string, len(AdminMapping))
|
|
i := 0
|
|
for k := range AdminMapping {
|
|
methods[i] = k
|
|
i++
|
|
}
|
|
return methods
|
|
}
|
|
|
|
// Execute given request
|
|
func (self *adminApi) Execute(req *shared.Request) (interface{}, error) {
|
|
if callback, ok := AdminMapping[req.Method]; ok {
|
|
return callback(self, req)
|
|
}
|
|
|
|
return nil, &shared.NotImplementedError{req.Method}
|
|
}
|
|
|
|
func (self *adminApi) Name() string {
|
|
return shared.AdminApiName
|
|
}
|
|
|
|
func (self *adminApi) ApiVersion() string {
|
|
return AdminApiversion
|
|
}
|
|
|
|
func (self *adminApi) AddPeer(req *shared.Request) (interface{}, error) {
|
|
args := new(AddPeerArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
err := self.ethereum.AddPeer(args.Url)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func (self *adminApi) Peers(req *shared.Request) (interface{}, error) {
|
|
return self.ethereum.PeersInfo(), nil
|
|
}
|
|
|
|
func (self *adminApi) NodeInfo(req *shared.Request) (interface{}, error) {
|
|
return self.ethereum.NodeInfo(), nil
|
|
}
|
|
|
|
func (self *adminApi) DataDir(req *shared.Request) (interface{}, error) {
|
|
return self.ethereum.DataDir, nil
|
|
}
|
|
|
|
func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool {
|
|
for _, b := range bs {
|
|
if !chain.HasBlock(b.Hash()) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (self *adminApi) ImportChain(req *shared.Request) (interface{}, error) {
|
|
args := new(ImportExportChainArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
fh, err := os.Open(args.Filename)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer fh.Close()
|
|
stream := rlp.NewStream(fh, 0)
|
|
|
|
// Run actual the import.
|
|
blocks := make(types.Blocks, importBatchSize)
|
|
n := 0
|
|
for batch := 0; ; batch++ {
|
|
|
|
i := 0
|
|
for ; i < importBatchSize; i++ {
|
|
var b types.Block
|
|
if err := stream.Decode(&b); err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return false, fmt.Errorf("at block %d: %v", n, err)
|
|
}
|
|
blocks[i] = &b
|
|
n++
|
|
}
|
|
if i == 0 {
|
|
break
|
|
}
|
|
// Import the batch.
|
|
if hasAllBlocks(self.ethereum.BlockChain(), blocks[:i]) {
|
|
continue
|
|
}
|
|
if _, err := self.ethereum.BlockChain().InsertChain(blocks[:i]); err != nil {
|
|
return false, fmt.Errorf("invalid block %d: %v", n, err)
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) ExportChain(req *shared.Request) (interface{}, error) {
|
|
args := new(ImportExportChainArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
fh, err := os.OpenFile(args.Filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer fh.Close()
|
|
if err := self.ethereum.BlockChain().Export(fh); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) Verbosity(req *shared.Request) (interface{}, error) {
|
|
args := new(VerbosityArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
glog.SetV(args.Level)
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) {
|
|
args := new(SetSolcArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
solc, err := self.xeth.SetSolc(args.Path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return solc.Info(), nil
|
|
}
|
|
|
|
func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) {
|
|
args := new(StartRPCArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
cfg := comms.HttpConfig{
|
|
ListenAddress: args.ListenAddress,
|
|
ListenPort: args.ListenPort,
|
|
CorsDomain: args.CorsDomain,
|
|
}
|
|
|
|
apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.ethereum, self.docRoot)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
err = comms.StartHttp(cfg, self.codec, Merge(apis...))
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func (self *adminApi) StopRPC(req *shared.Request) (interface{}, error) {
|
|
comms.StopHttp()
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) SleepBlocks(req *shared.Request) (interface{}, error) {
|
|
args := new(SleepBlocksArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
var timer <-chan time.Time
|
|
var height *big.Int
|
|
var err error
|
|
if args.Timeout > 0 {
|
|
timer = time.NewTimer(time.Duration(args.Timeout) * time.Second).C
|
|
}
|
|
|
|
height = new(big.Int).Add(self.xeth.CurrentBlock().Number(), big.NewInt(args.N))
|
|
height, err = sleepBlocks(self.xeth.UpdateState(), height, timer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return height.Uint64(), nil
|
|
}
|
|
|
|
func sleepBlocks(wait chan *big.Int, height *big.Int, timer <-chan time.Time) (newHeight *big.Int, err error) {
|
|
wait <- height
|
|
select {
|
|
case <-timer:
|
|
// if times out make sure the xeth loop does not block
|
|
go func() {
|
|
select {
|
|
case wait <- nil:
|
|
case <-wait:
|
|
}
|
|
}()
|
|
return nil, fmt.Errorf("timeout")
|
|
case newHeight = <-wait:
|
|
}
|
|
return
|
|
}
|
|
|
|
func (self *adminApi) Sleep(req *shared.Request) (interface{}, error) {
|
|
args := new(SleepArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
time.Sleep(time.Duration(args.S) * time.Second)
|
|
return nil, nil
|
|
}
|
|
|
|
func (self *adminApi) SetGlobalRegistrar(req *shared.Request) (interface{}, error) {
|
|
args := new(SetGlobalRegistrarArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
sender := common.HexToAddress(args.ContractAddress)
|
|
|
|
reg := registrar.New(self.xeth)
|
|
txhash, err := reg.SetGlobalRegistrar(args.NameReg, sender)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return txhash, nil
|
|
}
|
|
|
|
func (self *adminApi) SetHashReg(req *shared.Request) (interface{}, error) {
|
|
args := new(SetHashRegArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
reg := registrar.New(self.xeth)
|
|
sender := common.HexToAddress(args.Sender)
|
|
txhash, err := reg.SetHashReg(args.HashReg, sender)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return txhash, nil
|
|
}
|
|
|
|
func (self *adminApi) SetUrlHint(req *shared.Request) (interface{}, error) {
|
|
args := new(SetUrlHintArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
urlHint := args.UrlHint
|
|
sender := common.HexToAddress(args.Sender)
|
|
|
|
reg := registrar.New(self.xeth)
|
|
txhash, err := reg.SetUrlHint(urlHint, sender)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return txhash, nil
|
|
}
|
|
|
|
func (self *adminApi) SaveInfo(req *shared.Request) (interface{}, error) {
|
|
args := new(SaveInfoArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
contenthash, err := compiler.SaveInfo(&args.ContractInfo, args.Filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return contenthash.Hex(), nil
|
|
}
|
|
|
|
func (self *adminApi) Register(req *shared.Request) (interface{}, error) {
|
|
args := new(RegisterArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
sender := common.HexToAddress(args.Sender)
|
|
// sender and contract address are passed as hex strings
|
|
codeb := self.xeth.CodeAtBytes(args.Address)
|
|
codeHash := common.BytesToHash(crypto.Sha3(codeb))
|
|
contentHash := common.HexToHash(args.ContentHashHex)
|
|
registry := registrar.New(self.xeth)
|
|
|
|
_, err := registry.SetHashToHash(sender, codeHash, contentHash)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) RegisterUrl(req *shared.Request) (interface{}, error) {
|
|
args := new(RegisterUrlArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
sender := common.HexToAddress(args.Sender)
|
|
registry := registrar.New(self.xeth)
|
|
_, err := registry.SetUrlToHash(sender, common.HexToHash(args.ContentHash), args.Url)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) StartNatSpec(req *shared.Request) (interface{}, error) {
|
|
self.ethereum.NatSpec = true
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) StopNatSpec(req *shared.Request) (interface{}, error) {
|
|
self.ethereum.NatSpec = false
|
|
return true, nil
|
|
}
|
|
|
|
func (self *adminApi) GetContractInfo(req *shared.Request) (interface{}, error) {
|
|
args := new(GetContractInfoArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
infoDoc, err := natspec.FetchDocsForContract(args.Contract, self.xeth, self.ds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var info interface{}
|
|
err = self.coder.Decode(infoDoc, &info)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
|
|
func (self *adminApi) HttpGet(req *shared.Request) (interface{}, error) {
|
|
args := new(HttpGetArgs)
|
|
if err := self.coder.Decode(req.Params, &args); err != nil {
|
|
return nil, shared.NewDecodeParamError(err.Error())
|
|
}
|
|
|
|
resp, err := self.ds.Get(args.Uri, args.Path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return string(resp), nil
|
|
}
|
|
|
|
func (self *adminApi) EnableUserAgent(req *shared.Request) (interface{}, error) {
|
|
if fe, ok := self.xeth.Frontend().(*useragent.RemoteFrontend); ok {
|
|
fe.Enable()
|
|
}
|
|
return true, nil
|
|
}
|