From 821e01b0139eee9bfab9647e4ac1f2d6f1fb01bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= <peterke@gmail.com>
Date: Fri, 19 Jun 2015 18:13:49 +0300
Subject: [PATCH] cmd/geth, eth/fetcher: initial metrics support

Conflicts:
	cmd/geth/admin.go
---
 cmd/geth/admin.go      | 960 +++++++++++++++++++++++++++++++++++++++++
 eth/fetcher/fetcher.go |  14 +
 2 files changed, 974 insertions(+)
 create mode 100644 cmd/geth/admin.go

diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go
new file mode 100644
index 000000000..0d5e02523
--- /dev/null
+++ b/cmd/geth/admin.go
@@ -0,0 +1,960 @@
+package main
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"math/big"
+	"strconv"
+	"time"
+
+	"github.com/rcrowley/go-metrics"
+
+	"github.com/ethereum/ethash"
+	"github.com/ethereum/go-ethereum/accounts"
+	"github.com/ethereum/go-ethereum/cmd/utils"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/compiler"
+	"github.com/ethereum/go-ethereum/common/natspec"
+	"github.com/ethereum/go-ethereum/common/resolver"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core/vm"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/logger/glog"
+	"github.com/ethereum/go-ethereum/rlp"
+	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/ethereum/go-ethereum/xeth"
+	"github.com/robertkrimen/otto"
+	"gopkg.in/fatih/set.v0"
+)
+
+/*
+node admin bindings
+*/
+
+func (js *jsre) adminBindings() {
+	ethO, _ := js.re.Get("eth")
+	eth := ethO.Object()
+	eth.Set("pendingTransactions", js.pendingTransactions)
+	eth.Set("resend", js.resend)
+	eth.Set("sign", js.sign)
+
+	js.re.Set("admin", struct{}{})
+	t, _ := js.re.Get("admin")
+	admin := t.Object()
+	admin.Set("addPeer", js.addPeer)
+	admin.Set("startRPC", js.startRPC)
+	admin.Set("stopRPC", js.stopRPC)
+	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("verbosity", js.verbosity)
+	admin.Set("progress", js.syncProgress)
+	admin.Set("setSolc", js.setSolc)
+
+	admin.Set("contractInfo", struct{}{})
+	t, _ = admin.Get("contractInfo")
+	cinfo := t.Object()
+	// newRegistry officially not documented temporary option
+	cinfo.Set("start", js.startNatSpec)
+	cinfo.Set("stop", js.stopNatSpec)
+	cinfo.Set("newRegistry", js.newRegistry)
+	cinfo.Set("get", js.getContractInfo)
+	cinfo.Set("register", js.register)
+	cinfo.Set("registerUrl", js.registerUrl)
+	// cinfo.Set("verify", js.verify)
+
+	admin.Set("miner", struct{}{})
+	t, _ = admin.Get("miner")
+	miner := t.Object()
+	miner.Set("start", js.startMining)
+	miner.Set("stop", js.stopMining)
+	miner.Set("hashrate", js.hashrate)
+	miner.Set("setExtra", js.setExtra)
+	miner.Set("setGasPrice", js.setGasPrice)
+	miner.Set("startAutoDAG", js.startAutoDAG)
+	miner.Set("stopAutoDAG", js.stopAutoDAG)
+	miner.Set("makeDAG", js.makeDAG)
+
+	admin.Set("txPool", struct{}{})
+	t, _ = admin.Get("txPool")
+	txPool := t.Object()
+	txPool.Set("pending", js.allPendingTransactions)
+	txPool.Set("queued", js.allQueuedTransactions)
+
+	admin.Set("debug", struct{}{})
+	t, _ = admin.Get("debug")
+	debug := t.Object()
+	js.re.Set("sleep", js.sleep)
+	debug.Set("backtrace", js.backtrace)
+	debug.Set("printBlock", js.printBlock)
+	debug.Set("dumpBlock", js.dumpBlock)
+	debug.Set("getBlockRlp", js.getBlockRlp)
+	debug.Set("setHead", js.setHead)
+	debug.Set("processBlock", js.debugBlock)
+	debug.Set("seedhash", js.seedHash)
+	debug.Set("insertBlock", js.insertBlockRlp)
+	// undocumented temporary
+	debug.Set("waitForBlocks", js.waitForBlocks)
+
+	admin.Set("metrics", js.metrics)
+}
+
+// generic helper to getBlock by Number/Height or Hex depending on autodetected input
+// if argument is missing the current block is returned
+// if block is not found or there is problem with decoding
+// the appropriate value is returned and block is guaranteed to be nil
+func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) {
+	var block *types.Block
+	if len(call.ArgumentList) > 0 {
+		if call.Argument(0).IsNumber() {
+			num, _ := call.Argument(0).ToInteger()
+			block = js.ethereum.ChainManager().GetBlockByNumber(uint64(num))
+		} else if call.Argument(0).IsString() {
+			hash, _ := call.Argument(0).ToString()
+			block = js.ethereum.ChainManager().GetBlock(common.HexToHash(hash))
+		} else {
+			return nil, errors.New("invalid argument for dump. Either hex string or number")
+		}
+	} else {
+		block = js.ethereum.ChainManager().CurrentBlock()
+	}
+
+	if block == nil {
+		return nil, errors.New("block not found")
+	}
+	return block, nil
+}
+
+func (js *jsre) seedHash(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) > 0 {
+		if call.Argument(0).IsNumber() {
+			num, _ := call.Argument(0).ToInteger()
+			hash, err := ethash.GetSeedHash(uint64(num))
+			if err != nil {
+				fmt.Println(err)
+				return otto.UndefinedValue()
+			}
+			v, _ := call.Otto.ToValue(fmt.Sprintf("0x%x", hash))
+			return v
+		} else {
+			fmt.Println("arg not a number")
+		}
+	} else {
+		fmt.Println("requires number argument")
+	}
+
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) allPendingTransactions(call otto.FunctionCall) otto.Value {
+	txs := js.ethereum.TxPool().GetTransactions()
+
+	ltxs := make([]*tx, len(txs))
+	for i, tx := range txs {
+		// no need to check err
+		ltxs[i] = newTx(tx)
+	}
+
+	v, _ := call.Otto.ToValue(ltxs)
+	return v
+}
+
+func (js *jsre) allQueuedTransactions(call otto.FunctionCall) otto.Value {
+	txs := js.ethereum.TxPool().GetQueuedTransactions()
+
+	ltxs := make([]*tx, len(txs))
+	for i, tx := range txs {
+		// no need to check err
+		ltxs[i] = newTx(tx)
+	}
+
+	v, _ := call.Otto.ToValue(ltxs)
+	return v
+}
+
+func (js *jsre) pendingTransactions(call otto.FunctionCall) otto.Value {
+	txs := js.ethereum.TxPool().GetTransactions()
+
+	// grab the accounts from the account manager. This will help with determening which
+	// transactions should be returned.
+	accounts, err := js.ethereum.AccountManager().Accounts()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	// Add the accouns to a new set
+	accountSet := set.New()
+	for _, account := range accounts {
+		accountSet.Add(account.Address)
+	}
+
+	//ltxs := make([]*tx, len(txs))
+	var ltxs []*tx
+	for _, tx := range txs {
+		if from, _ := tx.From(); accountSet.Has(from) {
+			ltxs = append(ltxs, newTx(tx))
+		}
+	}
+
+	v, _ := call.Otto.ToValue(ltxs)
+	return v
+}
+
+func (js *jsre) resend(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) == 0 {
+		fmt.Println("first argument must be a transaction")
+		return otto.FalseValue()
+	}
+
+	v, err := call.Argument(0).Export()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	if tx, ok := v.(*tx); ok {
+		gl, gp := tx.GasLimit, tx.GasPrice
+		if len(call.ArgumentList) > 1 {
+			gp = call.Argument(1).String()
+		}
+		if len(call.ArgumentList) > 2 {
+			gl = call.Argument(2).String()
+		}
+
+		ret, err := js.xeth.Transact(tx.From, tx.To, tx.Nonce, tx.Value, gl, gp, tx.Data)
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+		js.ethereum.TxPool().RemoveTransactions(types.Transactions{tx.tx})
+
+		v, _ := call.Otto.ToValue(ret)
+		return v
+	}
+
+	fmt.Println("first argument must be a transaction")
+	return otto.FalseValue()
+}
+
+func (js *jsre) sign(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) != 2 {
+		fmt.Println("requires 2 arguments: eth.sign(signer, data)")
+		return otto.UndefinedValue()
+	}
+	signer, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	data, err := call.Argument(1).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	signed, err := js.xeth.Sign(signer, data, false)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	v, _ := call.Otto.ToValue(signed)
+	return v
+}
+
+func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value {
+	block, err := js.getBlock(call)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	tstart := time.Now()
+	old := vm.Debug
+
+	if len(call.ArgumentList) > 1 {
+		vm.Debug, _ = call.Argument(1).ToBoolean()
+	}
+
+	_, err = js.ethereum.BlockProcessor().RetryProcess(block)
+	if err != nil {
+		fmt.Println(err)
+		r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()})
+		return r
+	}
+	vm.Debug = old
+
+	r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()})
+	return r
+}
+
+func (js *jsre) insertBlockRlp(call otto.FunctionCall) otto.Value {
+	tstart := time.Now()
+
+	var block types.Block
+	if call.Argument(0).IsString() {
+		blockRlp, _ := call.Argument(0).ToString()
+		err := rlp.DecodeBytes(common.Hex2Bytes(blockRlp), &block)
+		if err != nil {
+			fmt.Println(err)
+			return otto.UndefinedValue()
+		}
+	}
+
+	old := vm.Debug
+	vm.Debug = true
+	_, err := js.ethereum.BlockProcessor().RetryProcess(&block)
+	if err != nil {
+		fmt.Println(err)
+		r, _ := call.Otto.ToValue(map[string]interface{}{"success": false, "time": time.Since(tstart).Seconds()})
+		return r
+	}
+	vm.Debug = old
+
+	r, _ := call.Otto.ToValue(map[string]interface{}{"success": true, "time": time.Since(tstart).Seconds()})
+	return r
+}
+
+func (js *jsre) setHead(call otto.FunctionCall) otto.Value {
+	block, err := js.getBlock(call)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	js.ethereum.ChainManager().SetHead(block)
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) syncProgress(call otto.FunctionCall) otto.Value {
+	pending, cached, importing, eta := js.ethereum.Downloader().Stats()
+	v, _ := call.Otto.ToValue(map[string]interface{}{
+		"pending":   pending,
+		"cached":    cached,
+		"importing": importing,
+		"estimate":  (eta / time.Second * time.Second).String(),
+	})
+	return v
+}
+
+func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value {
+	block, err := js.getBlock(call)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	encoded, _ := rlp.EncodeToBytes(block)
+	v, _ := call.Otto.ToValue(fmt.Sprintf("%x", encoded))
+	return v
+}
+
+func (js *jsre) setExtra(call otto.FunctionCall) otto.Value {
+	extra, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	if len(extra) > 1024 {
+		fmt.Println("error: cannot exceed 1024 bytes")
+		return otto.UndefinedValue()
+	}
+
+	js.ethereum.Miner().SetExtra([]byte(extra))
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) setGasPrice(call otto.FunctionCall) otto.Value {
+	gasPrice, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	js.ethereum.Miner().SetGasPrice(common.String2Big(gasPrice))
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) hashrate(call otto.FunctionCall) otto.Value {
+	v, _ := call.Otto.ToValue(js.ethereum.Miner().HashRate())
+	return v
+}
+
+func (js *jsre) makeDAG(call otto.FunctionCall) otto.Value {
+	blockNumber, err := call.Argument(1).ToInteger()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	err = ethash.MakeDAG(uint64(blockNumber), "")
+	if err != nil {
+		return otto.FalseValue()
+	}
+	return otto.TrueValue()
+}
+
+func (js *jsre) startAutoDAG(otto.FunctionCall) otto.Value {
+	js.ethereum.StartAutoDAG()
+	return otto.TrueValue()
+}
+
+func (js *jsre) stopAutoDAG(otto.FunctionCall) otto.Value {
+	js.ethereum.StopAutoDAG()
+	return otto.TrueValue()
+}
+
+func (js *jsre) backtrace(call otto.FunctionCall) otto.Value {
+	tracestr, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	glog.GetTraceLocation().Set(tracestr)
+
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) verbosity(call otto.FunctionCall) otto.Value {
+	v, err := call.Argument(0).ToInteger()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	glog.SetV(int(v))
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) startMining(call otto.FunctionCall) otto.Value {
+	var (
+		threads int64
+		err     error
+	)
+
+	if len(call.ArgumentList) > 0 {
+		threads, err = call.Argument(0).ToInteger()
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+	} else {
+		threads = int64(js.ethereum.MinerThreads)
+	}
+
+	// switch on DAG autogeneration when miner starts
+	js.ethereum.StartAutoDAG()
+
+	err = js.ethereum.StartMining(int(threads))
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	return otto.TrueValue()
+}
+
+func (js *jsre) stopMining(call otto.FunctionCall) otto.Value {
+	js.ethereum.StopMining()
+	js.ethereum.StopAutoDAG()
+	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()
+	}
+
+	corsDomain := js.corsDomain
+	if len(call.ArgumentList) > 2 {
+		corsDomain, err = call.Argument(2).ToString()
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+	}
+
+	config := rpc.RpcConfig{
+		ListenAddress: addr,
+		ListenPort:    uint(port),
+		CorsDomain:    corsDomain,
+	}
+
+	xeth := xeth.New(js.ethereum, nil)
+	err = rpc.Start(xeth, config)
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	return otto.TrueValue()
+}
+
+func (js *jsre) stopRPC(call otto.FunctionCall) otto.Value {
+	if rpc.Stop() == nil {
+		return otto.TrueValue()
+	}
+	return otto.FalseValue()
+}
+
+func (js *jsre) addPeer(call otto.FunctionCall) otto.Value {
+	nodeURL, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+	err = js.ethereum.AddPeer(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()
+	}
+	if seconds == 0 {
+		seconds = accounts.DefaultAccountUnlockDuration
+	}
+
+	arg := call.Argument(1)
+	var passphrase string
+	if arg.IsUndefined() {
+		fmt.Println("Please enter a passphrase now.")
+		passphrase, err = utils.PromptPassword("Passphrase: ", true)
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+	} else {
+		passphrase, err = arg.ToString()
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+	}
+	am := js.ethereum.AccountManager()
+	err = am.TimedUnlock(common.HexToAddress(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 := utils.PromptPassword("Passphrase: ", true)
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+		confirm, err := utils.PromptPassword("Repeat Passphrase: ", false)
+		if err != nil {
+			fmt.Println(err)
+			return otto.FalseValue()
+		}
+		if auth != confirm {
+			fmt.Println("Passphrases did not match.")
+			return otto.FalseValue()
+		}
+		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()
+	}
+	v, _ := call.Otto.ToValue(acct.Address.Hex())
+	return v
+}
+
+func (js *jsre) nodeInfo(call otto.FunctionCall) otto.Value {
+	v, _ := call.Otto.ToValue(js.ethereum.NodeInfo())
+	return v
+}
+
+func (js *jsre) peers(call otto.FunctionCall) otto.Value {
+	v, _ := call.Otto.ToValue(js.ethereum.PeersInfo())
+	return v
+}
+
+func (js *jsre) importChain(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) == 0 {
+		fmt.Println("require file name. admin.importChain(filename)")
+		return otto.FalseValue()
+	}
+	fn, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+	if err := utils.ImportChain(js.ethereum.ChainManager(), fn); err != nil {
+		fmt.Println("Import error: ", err)
+		return otto.FalseValue()
+	}
+	return otto.TrueValue()
+}
+
+func (js *jsre) exportChain(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) == 0 {
+		fmt.Println("require file name: admin.exportChain(filename)")
+		return otto.FalseValue()
+	}
+
+	fn, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+	if err := utils.ExportChain(js.ethereum.ChainManager(), fn); err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+	return otto.TrueValue()
+}
+
+func (js *jsre) printBlock(call otto.FunctionCall) otto.Value {
+	block, err := js.getBlock(call)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	fmt.Println(block)
+
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) dumpBlock(call otto.FunctionCall) otto.Value {
+	block, err := js.getBlock(call)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	statedb := state.New(block.Root(), js.ethereum.StateDb())
+	dump := statedb.RawDump()
+	v, _ := call.Otto.ToValue(dump)
+	return v
+}
+
+func (js *jsre) waitForBlocks(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) > 2 {
+		fmt.Println("requires 0, 1 or 2 arguments: admin.debug.waitForBlock(minHeight, timeout)")
+		return otto.FalseValue()
+	}
+	var n, timeout int64
+	var timer <-chan time.Time
+	var height *big.Int
+	var err error
+	args := len(call.ArgumentList)
+	if args == 2 {
+		timeout, err = call.Argument(1).ToInteger()
+		if err != nil {
+			fmt.Println(err)
+			return otto.UndefinedValue()
+		}
+		timer = time.NewTimer(time.Duration(timeout) * time.Second).C
+	}
+	if args >= 1 {
+		n, err = call.Argument(0).ToInteger()
+		if err != nil {
+			fmt.Println(err)
+			return otto.UndefinedValue()
+		}
+		height = big.NewInt(n)
+	}
+
+	if args == 0 {
+		height = js.xeth.CurrentBlock().Number()
+		height.Add(height, common.Big1)
+	}
+
+	wait := js.wait
+	js.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 otto.UndefinedValue()
+	case height = <-wait:
+	}
+	v, _ := call.Otto.ToValue(height.Uint64())
+	return v
+}
+
+func (js *jsre) metrics(call otto.FunctionCall) otto.Value {
+	// Iterate over all the metrics, and just dump for now
+	counters := make(map[string]interface{})
+	metrics.DefaultRegistry.Each(func(name string, metric interface{}) {
+		switch metric := metric.(type) {
+		case metrics.Meter:
+			counters[name+"( 1 min)"] = int(metric.Rate1() * 60)
+			counters[name+"( 5 min)"] = int(metric.Rate5() * 300)
+			counters[name+"(15 min)"] = int(metric.Rate15() * 900)
+
+		default:
+			counters[name] = "Unknown metric type"
+		}
+	})
+	// Flatten the counters into some metrics and return
+	v, _ := call.Otto.ToValue(counters)
+	return v
+}
+
+func (js *jsre) sleep(call otto.FunctionCall) otto.Value {
+	sec, err := call.Argument(0).ToInteger()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+	time.Sleep(time.Duration(sec) * time.Second)
+	return otto.UndefinedValue()
+}
+
+func (js *jsre) setSolc(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) != 1 {
+		fmt.Println("needs 1 argument: admin.contractInfo.setSolc(solcPath)")
+		return otto.FalseValue()
+	}
+	solcPath, err := call.Argument(0).ToString()
+	if err != nil {
+		return otto.FalseValue()
+	}
+	solc, err := js.xeth.SetSolc(solcPath)
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+	fmt.Println(solc.Info())
+	return otto.TrueValue()
+}
+
+func (js *jsre) register(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) != 4 {
+		fmt.Println("requires 4 arguments: admin.contractInfo.register(fromaddress, contractaddress, contract, filename)")
+		return otto.UndefinedValue()
+	}
+	sender, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	address, err := call.Argument(1).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	raw, err := call.Argument(2).Export()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	jsonraw, err := json.Marshal(raw)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	var contract compiler.Contract
+	err = json.Unmarshal(jsonraw, &contract)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	filename, err := call.Argument(3).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	contenthash, err := compiler.ExtractInfo(&contract, filename)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	// sender and contract address are passed as hex strings
+	codeb := js.xeth.CodeAtBytes(address)
+	codehash := common.BytesToHash(crypto.Sha3(codeb))
+
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	registry := resolver.New(js.xeth)
+
+	_, err = registry.RegisterContentHash(common.HexToAddress(sender), codehash, contenthash)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+
+	v, _ := call.Otto.ToValue(contenthash.Hex())
+	return v
+}
+
+func (js *jsre) registerUrl(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) != 3 {
+		fmt.Println("requires 3 arguments: admin.contractInfo.register(fromaddress, contenthash, filename)")
+		return otto.FalseValue()
+	}
+	sender, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	contenthash, err := call.Argument(1).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	url, err := call.Argument(2).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	registry := resolver.New(js.xeth)
+
+	_, err = registry.RegisterUrl(common.HexToAddress(sender), common.HexToHash(contenthash), url)
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	return otto.TrueValue()
+}
+
+func (js *jsre) getContractInfo(call otto.FunctionCall) otto.Value {
+	if len(call.ArgumentList) != 1 {
+		fmt.Println("requires 1 argument: admin.contractInfo.register(contractaddress)")
+		return otto.FalseValue()
+	}
+	addr, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	infoDoc, err := natspec.FetchDocsForContract(addr, js.xeth, ds)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	var info compiler.ContractInfo
+	err = json.Unmarshal(infoDoc, &info)
+	if err != nil {
+		fmt.Println(err)
+		return otto.UndefinedValue()
+	}
+	v, _ := call.Otto.ToValue(info)
+	return v
+}
+
+func (js *jsre) startNatSpec(call otto.FunctionCall) otto.Value {
+	js.ethereum.NatSpec = true
+	return otto.TrueValue()
+}
+
+func (js *jsre) stopNatSpec(call otto.FunctionCall) otto.Value {
+	js.ethereum.NatSpec = false
+	return otto.TrueValue()
+}
+
+func (js *jsre) newRegistry(call otto.FunctionCall) otto.Value {
+
+	if len(call.ArgumentList) != 1 {
+		fmt.Println("requires 1 argument: admin.contractInfo.newRegistry(adminaddress)")
+		return otto.FalseValue()
+	}
+	addr, err := call.Argument(0).ToString()
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	registry := resolver.New(js.xeth)
+	err = registry.CreateContracts(common.HexToAddress(addr))
+	if err != nil {
+		fmt.Println(err)
+		return otto.FalseValue()
+	}
+
+	return otto.TrueValue()
+}
+
+// internal transaction type which will allow us to resend transactions  using `eth.resend`
+type tx struct {
+	tx *types.Transaction
+
+	To       string
+	From     string
+	Nonce    string
+	Value    string
+	Data     string
+	GasLimit string
+	GasPrice string
+}
+
+func newTx(t *types.Transaction) *tx {
+	from, _ := t.From()
+	var to string
+	if t := t.To(); t != nil {
+		to = t.Hex()
+	}
+
+	return &tx{
+		tx:       t,
+		To:       to,
+		From:     from.Hex(),
+		Value:    t.Amount.String(),
+		Nonce:    strconv.Itoa(int(t.Nonce())),
+		Data:     "0x" + common.Bytes2Hex(t.Data()),
+		GasLimit: t.GasLimit.String(),
+		GasPrice: t.GasPrice().String(),
+	}
+}
diff --git a/eth/fetcher/fetcher.go b/eth/fetcher/fetcher.go
index 90a202235..a9f4227c4 100644
--- a/eth/fetcher/fetcher.go
+++ b/eth/fetcher/fetcher.go
@@ -7,6 +7,8 @@ import (
 	"math/rand"
 	"time"
 
+	"github.com/rcrowley/go-metrics"
+
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/types"
 	"github.com/ethereum/go-ethereum/logger"
@@ -96,6 +98,11 @@ type Fetcher struct {
 	// Testing hooks
 	fetchingHook func([]common.Hash) // Method to call upon starting a block fetch
 	importedHook func(*types.Block)  // Method to call upon successful block import
+
+	// Runtime metrics
+	announceStats  metrics.Meter
+	broadcastStats metrics.Meter
+	discardStats   metrics.Meter
 }
 
 // New creates a block fetcher to retrieve blocks based on hash announcements.
@@ -118,6 +125,9 @@ func New(getBlock blockRetrievalFn, validateBlock blockValidatorFn, broadcastBlo
 		chainHeight:    chainHeight,
 		insertChain:    insertChain,
 		dropPeer:       dropPeer,
+		announceStats:  metrics.GetOrRegisterMeter("eth/Announced Blocks", metrics.DefaultRegistry),
+		broadcastStats: metrics.GetOrRegisterMeter("eth/Propagated Blocks", metrics.DefaultRegistry),
+		discardStats:   metrics.GetOrRegisterMeter("eth/Discarded Blocks", metrics.DefaultRegistry),
 	}
 }
 
@@ -229,6 +239,8 @@ func (f *Fetcher) loop() {
 
 		case notification := <-f.notify:
 			// A block was announced, make sure the peer isn't DOSing us
+			f.announceStats.Mark(1)
+
 			count := f.announces[notification.origin] + 1
 			if count > hashLimit {
 				glog.V(logger.Debug).Infof("Peer %s: exceeded outstanding announces (%d)", notification.origin, hashLimit)
@@ -246,6 +258,7 @@ func (f *Fetcher) loop() {
 
 		case op := <-f.inject:
 			// A direct block insertion was requested, try and fill any pending gaps
+			f.broadcastStats.Mark(1)
 			f.enqueue(op.origin, op.block)
 
 		case hash := <-f.done:
@@ -364,6 +377,7 @@ func (f *Fetcher) enqueue(peer string, block *types.Block) {
 	// Discard any past or too distant blocks
 	if dist := int64(block.NumberU64()) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist {
 		glog.V(logger.Debug).Infof("Peer %s: discarded block #%d [%x], distance %d", peer, block.NumberU64(), hash.Bytes()[:4], dist)
+		f.discardStats.Mark(1)
 		return
 	}
 	// Schedule the block for future importing