From 4d005a2c1d2929dc770acd3a2bfed59495c70557 Mon Sep 17 00:00:00 2001
From: zelig <viktor.tron@gmail.com>
Date: Mon, 26 Oct 2015 22:24:09 +0100
Subject: rpc api: eth_getNatSpec * xeth, rpc: implement eth_getNatSpec for tx
 confirmations * rename silly docserver -> httpclient * eth/backend:
 httpclient now accessible via eth.Ethereum init-d via config.DocRoot * cmd:
 introduce separate CLI flag for DocRoot (defaults to homedir) * common/path:
 delete unused assetpath func, separate HomeDir func

---
 cmd/geth/js.go                       |  31 ++++-----
 cmd/geth/js_test.go                  |  10 +--
 cmd/utils/flags.go                   |  10 ++-
 common/docserver/docserver.go        | 120 ---------------------------------
 common/docserver/docserver_test.go   |  77 ----------------------
 common/httpclient/httpclient.go      | 124 +++++++++++++++++++++++++++++++++++
 common/httpclient/httpclient_test.go |  77 ++++++++++++++++++++++
 common/natspec/natspec.go            |  14 ++--
 common/natspec/natspec_e2e_test.go   |   6 +-
 common/path.go                       |  44 ++-----------
 eth/backend.go                       |  11 ++++
 rpc/api/admin.go                     |  13 ++--
 rpc/api/api_test.go                  |   8 +--
 rpc/api/eth.go                       |  14 ++++
 rpc/api/eth_js.go                    |   6 ++
 rpc/api/utils.go                     |   5 +-
 16 files changed, 285 insertions(+), 285 deletions(-)
 delete mode 100644 common/docserver/docserver.go
 delete mode 100644 common/docserver/docserver_test.go
 create mode 100644 common/httpclient/httpclient.go
 create mode 100644 common/httpclient/httpclient_test.go

diff --git a/cmd/geth/js.go b/cmd/geth/js.go
index 7f7f19d78..4d5462539 100644
--- a/cmd/geth/js.go
+++ b/cmd/geth/js.go
@@ -30,7 +30,6 @@ import (
 
 	"github.com/ethereum/go-ethereum/cmd/utils"
 	"github.com/ethereum/go-ethereum/common"
-	"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/eth"
@@ -77,8 +76,6 @@ func (r dumbterm) PasswordPrompt(p string) (string, error) {
 func (r dumbterm) AppendHistory(string) {}
 
 type jsre struct {
-	docRoot    string
-	ds         *docserver.DocServer
 	re         *re.JSRE
 	ethereum   *eth.Ethereum
 	xeth       *xeth.XEth
@@ -153,7 +150,6 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str
 	js := &jsre{ps1: "> "}
 	js.wait = make(chan *big.Int)
 	js.client = client
-	js.ds = docserver.New(docRoot)
 
 	// update state in separare forever blocks
 	js.re = re.New(docRoot)
@@ -181,18 +177,17 @@ func newLightweightJSRE(docRoot string, client comms.EthereumClient, datadir str
 }
 
 func newJSRE(ethereum *eth.Ethereum, docRoot, corsDomain string, client comms.EthereumClient, interactive bool, f xeth.Frontend) *jsre {
-	js := &jsre{ethereum: ethereum, ps1: "> ", docRoot: docRoot}
+	js := &jsre{ethereum: ethereum, ps1: "> "}
 	// set default cors domain used by startRpc from CLI flag
 	js.corsDomain = corsDomain
 	if f == nil {
 		f = js
 	}
-	js.ds = docserver.New(docRoot)
 	js.xeth = xeth.New(ethereum, f)
 	js.wait = js.xeth.UpdateState()
 	js.client = client
 	if clt, ok := js.client.(*comms.InProcClient); ok {
-		if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, ethereum, docRoot); err == nil {
+		if offeredApis, err := api.ParseApiString(shared.AllApis, codec.JSON, js.xeth, ethereum); err == nil {
 			clt.Initialize(api.Merge(offeredApis...))
 		}
 	}
@@ -248,14 +243,14 @@ func (self *jsre) batch(statement string) {
 // show summary of current geth instance
 func (self *jsre) welcome() {
 	self.re.Run(`
-		(function () {
-			console.log('instance: ' + web3.version.client);
-			console.log(' datadir: ' + admin.datadir);
-			console.log("coinbase: " + eth.coinbase);
-			var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp;
-			console.log("at block: " + eth.blockNumber + " (" + new Date(ts) + ")");
-		})();
-	`)
+    (function () {
+      console.log('instance: ' + web3.version.client);
+      console.log(' datadir: ' + admin.datadir);
+      console.log("coinbase: " + eth.coinbase);
+      var ts = 1000 * eth.getBlock(eth.blockNumber).timestamp;
+      console.log("at block: " + eth.blockNumber + " (" + new Date(ts) + ")");
+    })();
+  `)
 	if modules, err := self.supportedApis(); err == nil {
 		loadedModules := make([]string, 0)
 		for api, version := range modules {
@@ -281,7 +276,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.ethereum, js.docRoot)
+	apiImpl, err := api.ParseApiString(strings.Join(apiNames, ","), codec.JSON, js.xeth, js.ethereum)
 	if err != nil {
 		utils.Fatalf("Unable to determine supported api's: %v", err)
 	}
@@ -334,7 +329,7 @@ func (js *jsre) apiBindings(f xeth.Frontend) error {
 		utils.Fatalf("Error setting namespaces: %v", err)
 	}
 
-	js.re.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `);	 registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
+	js.re.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `);   registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`)
 	return nil
 }
 
@@ -348,7 +343,7 @@ func (self *jsre) AskPassword() (string, bool) {
 
 func (self *jsre) ConfirmTransaction(tx string) bool {
 	if self.ethereum.NatSpec {
-		notice := natspec.GetNotice(self.xeth, tx, self.ds)
+		notice := natspec.GetNotice(self.xeth, tx, self.ethereum.HTTPClient())
 		fmt.Println(notice)
 		answer, _ := self.Prompt("Confirm Transaction [y/n]")
 		return strings.HasPrefix(strings.Trim(answer, " "), "y")
diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go
index 09cc88519..477079706 100644
--- a/cmd/geth/js_test.go
+++ b/cmd/geth/js_test.go
@@ -31,7 +31,7 @@ import (
 	"github.com/ethereum/go-ethereum/accounts"
 	"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/httpclient"
 	"github.com/ethereum/go-ethereum/common/natspec"
 	"github.com/ethereum/go-ethereum/common/registrar"
 	"github.com/ethereum/go-ethereum/core"
@@ -62,7 +62,7 @@ var (
 type testjethre struct {
 	*jsre
 	lastConfirm string
-	ds          *docserver.DocServer
+	client      *httpclient.HTTPClient
 }
 
 func (self *testjethre) UnlockAccount(acc []byte) bool {
@@ -75,7 +75,7 @@ func (self *testjethre) UnlockAccount(acc []byte) bool {
 
 func (self *testjethre) ConfirmTransaction(tx string) bool {
 	if self.ethereum.NatSpec {
-		self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.ds)
+		self.lastConfirm = natspec.GetNotice(self.xeth, tx, self.client)
 	}
 	return true
 }
@@ -101,6 +101,7 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth
 		AccountManager: am,
 		MaxPeers:       0,
 		Name:           "test",
+		DocRoot:        "/",
 		SolcPath:       testSolcPath,
 		PowTest:        true,
 		NewDB:          func(path string) (ethdb.Database, error) { return db, nil },
@@ -130,8 +131,7 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth
 
 	assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
 	client := comms.NewInProcClient(codec.JSON)
-	ds := docserver.New("/")
-	tf := &testjethre{ds: ds}
+	tf := &testjethre{client: ethereum.HTTPClient()}
 	repl := newJSRE(ethereum, assetPath, "", client, false, tf)
 	tf.jsre = repl
 	return tmp, tf, ethereum
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 66ec46f80..79c86c52a 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -139,6 +139,11 @@ var (
 		Name:  "natspec",
 		Usage: "Enable NatSpec confirmation notice",
 	}
+	DocRootFlag = DirectoryFlag{
+		Name:  "docroot",
+		Usage: "Document Root for HTTPClient file scheme",
+		Value: DirectoryString{common.HomeDir()},
+	}
 	CacheFlag = cli.IntFlag{
 		Name:  "cache",
 		Usage: "Megabytes of memory allocated to internal caching",
@@ -452,6 +457,7 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
 		Olympic:                 ctx.GlobalBool(OlympicFlag.Name),
 		NAT:                     MakeNAT(ctx),
 		NatSpec:                 ctx.GlobalBool(NatspecEnabledFlag.Name),
+		DocRoot:                 ctx.GlobalString(DocRootFlag.Name),
 		Discovery:               !ctx.GlobalBool(NoDiscoverFlag.Name),
 		NodeKey:                 MakeNodeKey(ctx),
 		Shh:                     ctx.GlobalBool(WhisperEnabledFlag.Name),
@@ -616,7 +622,7 @@ func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error {
 		xeth := xeth.New(eth, fe)
 		codec := codec.JSON
 
-		apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec, xeth, eth, ctx.GlobalString(JSpathFlag.Name))
+		apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec, xeth, eth)
 		if err != nil {
 			return nil, err
 		}
@@ -637,7 +643,7 @@ func StartRPC(eth *eth.Ethereum, ctx *cli.Context) error {
 	xeth := xeth.New(eth, nil)
 	codec := codec.JSON
 
-	apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, eth, ctx.GlobalString(JSpathFlag.Name))
+	apis, err := api.ParseApiString(ctx.GlobalString(RpcApiFlag.Name), codec, xeth, eth)
 	if err != nil {
 		return err
 	}
diff --git a/common/docserver/docserver.go b/common/docserver/docserver.go
deleted file mode 100644
index cfc4e3b26..000000000
--- a/common/docserver/docserver.go
+++ /dev/null
@@ -1,120 +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 docserver
-
-import (
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"path/filepath"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-type DocServer struct {
-	*http.Transport
-	DocRoot string
-	schemes []string
-}
-
-func New(docRoot string) (self *DocServer) {
-	self = &DocServer{
-		Transport: &http.Transport{},
-		DocRoot:   docRoot,
-		schemes:   []string{"file"},
-	}
-	self.RegisterProtocol("file", http.NewFileTransport(http.Dir(self.DocRoot)))
-	return
-}
-
-// Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines.
-
-// A Client is higher-level than a RoundTripper (such as Transport) and additionally handles HTTP details such as cookies and redirects.
-
-func (self *DocServer) Client() *http.Client {
-	return &http.Client{
-		Transport: self,
-	}
-}
-
-func (self *DocServer) RegisterScheme(scheme string, rt http.RoundTripper) {
-	self.schemes = append(self.schemes, scheme)
-	self.RegisterProtocol(scheme, rt)
-}
-
-func (self *DocServer) HasScheme(scheme string) bool {
-	for _, s := range self.schemes {
-		if s == scheme {
-			return true
-		}
-	}
-	return false
-}
-
-func (self *DocServer) GetAuthContent(uri string, hash common.Hash) (content []byte, err error) {
-	// retrieve content
-	content, err = self.Get(uri, "")
-	if err != nil {
-		return
-	}
-
-	// check hash to authenticate content
-	chash := crypto.Sha3Hash(content)
-	if chash != hash {
-		content = nil
-		err = fmt.Errorf("content hash mismatch %x != %x (exp)", hash[:], chash[:])
-	}
-
-	return
-
-}
-
-// Get(uri, path) downloads the document at uri, if path is non-empty it
-// is interpreted as a filepath to which the contents are saved
-func (self *DocServer) Get(uri, path string) (content []byte, err error) {
-	// retrieve content
-	resp, err := self.Client().Get(uri)
-
-	defer func() {
-		if resp != nil {
-			resp.Body.Close()
-		}
-	}()
-
-	if err != nil {
-		return
-	}
-
-	content, err = ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return
-	}
-
-	if resp.StatusCode/100 != 2 {
-		return content, fmt.Errorf("HTTP error: %s", resp.Status)
-	}
-
-	if path != "" {
-		var abspath string
-		abspath, err = filepath.Abs(path)
-		ioutil.WriteFile(abspath, content, 0700)
-	}
-
-	return
-
-}
diff --git a/common/docserver/docserver_test.go b/common/docserver/docserver_test.go
deleted file mode 100644
index 632603add..000000000
--- a/common/docserver/docserver_test.go
+++ /dev/null
@@ -1,77 +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 docserver
-
-import (
-	"io/ioutil"
-	"net/http"
-	"os"
-	"path"
-	"testing"
-
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/crypto"
-)
-
-func TestGetAuthContent(t *testing.T) {
-	dir, err := ioutil.TempDir("", "docserver-test")
-	if err != nil {
-		t.Fatal("cannot create temporary directory:", err)
-	}
-	defer os.RemoveAll(dir)
-	ds := New(dir)
-
-	text := "test"
-	hash := crypto.Sha3Hash([]byte(text))
-	if err := ioutil.WriteFile(path.Join(dir, "test.content"), []byte(text), os.ModePerm); err != nil {
-		t.Fatal("could not write test file", err)
-	}
-	content, err := ds.GetAuthContent("file:///test.content", hash)
-	if err != nil {
-		t.Errorf("no error expected, got %v", err)
-	}
-	if string(content) != text {
-		t.Errorf("incorrect content. expected %v, got %v", text, string(content))
-	}
-
-	hash = common.Hash{}
-	content, err = ds.GetAuthContent("file:///test.content", hash)
-	expected := "content hash mismatch 0000000000000000000000000000000000000000000000000000000000000000 != 9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658 (exp)"
-	if err == nil {
-		t.Errorf("expected error, got nothing")
-	} else {
-		if err.Error() != expected {
-			t.Errorf("expected error '%s' got '%v'", expected, err)
-		}
-	}
-
-}
-
-type rt struct{}
-
-func (rt) RoundTrip(req *http.Request) (resp *http.Response, err error) { return }
-
-func TestRegisterScheme(t *testing.T) {
-	ds := New("/tmp/")
-	if ds.HasScheme("scheme") {
-		t.Errorf("expected scheme not to be registered")
-	}
-	ds.RegisterScheme("scheme", rt{})
-	if !ds.HasScheme("scheme") {
-		t.Errorf("expected scheme to be registered")
-	}
-}
\ No newline at end of file
diff --git a/common/httpclient/httpclient.go b/common/httpclient/httpclient.go
new file mode 100644
index 000000000..23373ecaf
--- /dev/null
+++ b/common/httpclient/httpclient.go
@@ -0,0 +1,124 @@
+// 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 httpclient
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"path/filepath"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+type HTTPClient struct {
+	*http.Transport
+	DocRoot string
+	schemes []string
+}
+
+func New(docRoot string) (self *HTTPClient) {
+	self = &HTTPClient{
+		Transport: &http.Transport{},
+		DocRoot:   docRoot,
+		schemes:   []string{"file"},
+	}
+	self.RegisterProtocol("file", http.NewFileTransport(http.Dir(self.DocRoot)))
+	return
+}
+
+// Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines.
+
+// A Client is higher-level than a RoundTripper (such as Transport) and additionally handles HTTP details such as cookies and redirects.
+
+func (self *HTTPClient) Client() *http.Client {
+	return &http.Client{
+		Transport: self,
+	}
+}
+
+func (self *HTTPClient) RegisterScheme(scheme string, rt http.RoundTripper) {
+	self.schemes = append(self.schemes, scheme)
+	self.RegisterProtocol(scheme, rt)
+}
+
+func (self *HTTPClient) HasScheme(scheme string) bool {
+	for _, s := range self.schemes {
+		if s == scheme {
+			return true
+		}
+	}
+	return false
+}
+
+func (self *HTTPClient) GetAuthContent(uri string, hash common.Hash) ([]byte, error) {
+	// retrieve content
+	content, err := self.Get(uri, "")
+	if err != nil {
+		return nil, err
+	}
+
+	// check hash to authenticate content
+	chash := crypto.Sha3Hash(content)
+	if chash != hash {
+		return nil, fmt.Errorf("content hash mismatch %x != %x (exp)", hash[:], chash[:])
+	}
+
+	return content, nil
+
+}
+
+// Get(uri, path) downloads the document at uri, if path is non-empty it
+// is interpreted as a filepath to which the contents are saved
+func (self *HTTPClient) Get(uri, path string) ([]byte, error) {
+	// retrieve content
+	resp, err := self.Client().Get(uri)
+	if err != nil {
+		return nil, err
+	}
+	defer func() {
+		if resp != nil {
+			resp.Body.Close()
+		}
+	}()
+
+	var content []byte
+	content, err = ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.StatusCode/100 != 2 {
+		return content, fmt.Errorf("HTTP error: %s", resp.Status)
+	}
+
+	if path != "" {
+		var abspath string
+		abspath, err = filepath.Abs(path)
+		if err != nil {
+			return nil, err
+		}
+		err = ioutil.WriteFile(abspath, content, 0600)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return content, nil
+
+}
diff --git a/common/httpclient/httpclient_test.go b/common/httpclient/httpclient_test.go
new file mode 100644
index 000000000..6c3782e15
--- /dev/null
+++ b/common/httpclient/httpclient_test.go
@@ -0,0 +1,77 @@
+// 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 httpclient
+
+import (
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+func TestGetAuthContent(t *testing.T) {
+	dir, err := ioutil.TempDir("", "httpclient-test")
+	if err != nil {
+		t.Fatal("cannot create temporary directory:", err)
+	}
+	defer os.RemoveAll(dir)
+	client := New(dir)
+
+	text := "test"
+	hash := crypto.Sha3Hash([]byte(text))
+	if err := ioutil.WriteFile(path.Join(dir, "test.content"), []byte(text), os.ModePerm); err != nil {
+		t.Fatal("could not write test file", err)
+	}
+	content, err := client.GetAuthContent("file:///test.content", hash)
+	if err != nil {
+		t.Errorf("no error expected, got %v", err)
+	}
+	if string(content) != text {
+		t.Errorf("incorrect content. expected %v, got %v", text, string(content))
+	}
+
+	hash = common.Hash{}
+	content, err = client.GetAuthContent("file:///test.content", hash)
+	expected := "content hash mismatch 0000000000000000000000000000000000000000000000000000000000000000 != 9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658 (exp)"
+	if err == nil {
+		t.Errorf("expected error, got nothing")
+	} else {
+		if err.Error() != expected {
+			t.Errorf("expected error '%s' got '%v'", expected, err)
+		}
+	}
+
+}
+
+type rt struct{}
+
+func (rt) RoundTrip(req *http.Request) (resp *http.Response, err error) { return }
+
+func TestRegisterScheme(t *testing.T) {
+	client := New("/tmp/")
+	if client.HasScheme("scheme") {
+		t.Errorf("expected scheme not to be registered")
+	}
+	client.RegisterScheme("scheme", rt{})
+	if !client.HasScheme("scheme") {
+		t.Errorf("expected scheme to be registered")
+	}
+}
diff --git a/common/natspec/natspec.go b/common/natspec/natspec.go
index 0265c2e13..d9627b4e1 100644
--- a/common/natspec/natspec.go
+++ b/common/natspec/natspec.go
@@ -23,7 +23,7 @@ import (
 	"strings"
 
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/docserver"
+	"github.com/ethereum/go-ethereum/common/httpclient"
 	"github.com/ethereum/go-ethereum/common/registrar"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/ethereum/go-ethereum/xeth"
@@ -43,7 +43,7 @@ type NatSpec struct {
 // the implementation is frontend friendly in that it always gives back
 // a notice that is safe to display
 // :FIXME: the second return value is an error, which can be used to fine-tune bahaviour
-func GetNotice(xeth *xeth.XEth, tx string, http *docserver.DocServer) (notice string) {
+func GetNotice(xeth *xeth.XEth, tx string, http *httpclient.HTTPClient) (notice string) {
 	ns, err := New(xeth, tx, http)
 	if err != nil {
 		if ns == nil {
@@ -83,7 +83,7 @@ type contractInfo struct {
 	DeveloperDoc  json.RawMessage `json:"developerDoc"`
 }
 
-func New(xeth *xeth.XEth, jsontx string, http *docserver.DocServer) (self *NatSpec, err error) {
+func New(xeth *xeth.XEth, jsontx string, http *httpclient.HTTPClient) (self *NatSpec, err error) {
 
 	// extract contract address from tx
 	var tx jsonTx
@@ -104,7 +104,7 @@ func New(xeth *xeth.XEth, jsontx string, http *docserver.DocServer) (self *NatSp
 }
 
 // also called by admin.contractInfo.get
-func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, ds *docserver.DocServer) (content []byte, err error) {
+func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, client *httpclient.HTTPClient) (content []byte, err error) {
 	// retrieve contract hash from state
 	codehex := xeth.CodeAt(contractAddress)
 	codeb := xeth.CodeAtBytes(contractAddress)
@@ -122,8 +122,8 @@ func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, ds *docserver
 	if err != nil {
 		return
 	}
-	if ds.HasScheme("bzz") {
-		content, err = ds.Get("bzz://"+hash.Hex()[2:], "")
+	if client.HasScheme("bzz") {
+		content, err = client.Get("bzz://"+hash.Hex()[2:], "")
 		if err == nil { // non-fatal
 			return
 		}
@@ -137,7 +137,7 @@ func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, ds *docserver
 	}
 
 	// get content via http client and authenticate content using hash
-	content, err = ds.GetAuthContent(uri, hash)
+	content, err = client.GetAuthContent(uri, hash)
 	if err != nil {
 		return
 	}
diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go
index 4149314c3..706a294ec 100644
--- a/common/natspec/natspec_e2e_test.go
+++ b/common/natspec/natspec_e2e_test.go
@@ -28,7 +28,7 @@ import (
 
 	"github.com/ethereum/go-ethereum/accounts"
 	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/common/docserver"
+	"github.com/ethereum/go-ethereum/common/httpclient"
 	"github.com/ethereum/go-ethereum/common/registrar"
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/crypto"
@@ -113,8 +113,8 @@ func (self *testFrontend) UnlockAccount(acc []byte) bool {
 
 func (self *testFrontend) ConfirmTransaction(tx string) bool {
 	if self.wantNatSpec {
-		ds := docserver.New("/tmp/")
-		self.lastConfirm = GetNotice(self.xeth, tx, ds)
+		client := httpclient.New("/tmp/")
+		self.lastConfirm = GetNotice(self.xeth, tx, client)
 	}
 	return true
 }
diff --git a/common/path.go b/common/path.go
index 1253c424c..39eacacee 100644
--- a/common/path.go
+++ b/common/path.go
@@ -23,8 +23,6 @@ import (
 	"path/filepath"
 	"runtime"
 	"strings"
-
-	"github.com/kardianos/osext"
 )
 
 // MakeName creates a node name that follows the ethereum convention
@@ -65,48 +63,18 @@ func AbsolutePath(Datadir string, filename string) string {
 	return filepath.Join(Datadir, filename)
 }
 
-func DefaultAssetPath() string {
-	var assetPath string
-	pwd, _ := os.Getwd()
-	srcdir := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist")
-
-	// If the current working directory is the go-ethereum dir
-	// assume a debug build and use the source directory as
-	// asset directory.
-	if pwd == srcdir {
-		assetPath = filepath.Join(pwd, "assets")
+func HomeDir() (home string) {
+	if usr, err := user.Current(); err == nil {
+		home = usr.HomeDir
 	} else {
-		switch runtime.GOOS {
-		case "darwin":
-			// Get Binary Directory
-			exedir, _ := osext.ExecutableFolder()
-			assetPath = filepath.Join(exedir, "..", "Resources")
-		case "linux":
-			assetPath = filepath.Join("usr", "share", "mist")
-		case "windows":
-			assetPath = filepath.Join(".", "assets")
-		default:
-			assetPath = "."
-		}
-	}
-
-	// Check if the assetPath exists. If not, try the source directory
-	// This happens when binary is run from outside cmd/mist directory
-	if _, err := os.Stat(assetPath); os.IsNotExist(err) {
-		assetPath = filepath.Join(srcdir, "assets")
+		home = os.Getenv("HOME")
 	}
-
-	return assetPath
+	return
 }
 
 func DefaultDataDir() string {
 	// Try to place the data folder in the user's home dir
-	var home string
-	if usr, err := user.Current(); err == nil {
-		home = usr.HomeDir
-	} else {
-		home = os.Getenv("HOME")
-	}
+	home := HomeDir()
 	if home != "" {
 		if runtime.GOOS == "darwin" {
 			return filepath.Join(home, "Library", "Ethereum")
diff --git a/eth/backend.go b/eth/backend.go
index 6ce0d0eb0..ee857e146 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -35,6 +35,7 @@ import (
 	"github.com/ethereum/go-ethereum/accounts"
 	"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/core"
 	"github.com/ethereum/go-ethereum/core/state"
 	"github.com/ethereum/go-ethereum/core/types"
@@ -106,6 +107,7 @@ type Config struct {
 	LogJSON   string
 	VmDebug   bool
 	NatSpec   bool
+	DocRoot   string
 	AutoDAG   bool
 	PowTest   bool
 	ExtraData []byte
@@ -249,6 +251,8 @@ type Ethereum struct {
 	GpobaseStepUp           int
 	GpobaseCorrectionFactor int
 
+	httpclient *httpclient.HTTPClient
+
 	net      *p2p.Server
 	eventMux *event.TypeMux
 	miner    *miner.Miner
@@ -400,6 +404,7 @@ func New(config *Config) (*Ethereum, error) {
 		GpobaseStepDown:         config.GpobaseStepDown,
 		GpobaseStepUp:           config.GpobaseStepUp,
 		GpobaseCorrectionFactor: config.GpobaseCorrectionFactor,
+		httpclient:              httpclient.New(config.DocRoot),
 	}
 
 	if config.PowTest {
@@ -702,6 +707,12 @@ func (self *Ethereum) StopAutoDAG() {
 	glog.V(logger.Info).Infof("Automatic pregeneration of ethash DAG OFF (ethash dir: %s)", ethash.DefaultDir)
 }
 
+// HTTPClient returns the light http client used for fetching offchain docs
+// (natspec, source for verification)
+func (self *Ethereum) HTTPClient() *httpclient.HTTPClient {
+	return self.httpclient
+}
+
 func (self *Ethereum) Solc() (*compiler.Solidity, error) {
 	var err error
 	if self.solc == nil {
diff --git a/rpc/api/admin.go b/rpc/api/admin.go
index eed8d8366..eb08fbc5d 100644
--- a/rpc/api/admin.go
+++ b/rpc/api/admin.go
@@ -25,7 +25,6 @@ import (
 
 	"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"
@@ -84,19 +83,15 @@ type adminApi struct {
 	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 {
+func NewAdminApi(xeth *xeth.XEth, ethereum *eth.Ethereum, codec codec.Codec) *adminApi {
 	return &adminApi{
 		xeth:     xeth,
 		ethereum: ethereum,
 		codec:    codec,
 		coder:    codec.New(nil),
-		docRoot:  docRoot,
-		ds:       docserver.New(docRoot),
 	}
 }
 
@@ -258,7 +253,7 @@ func (self *adminApi) StartRPC(req *shared.Request) (interface{}, error) {
 		CorsDomain:    args.CorsDomain,
 	}
 
-	apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.ethereum, self.docRoot)
+	apis, err := ParseApiString(args.Apis, self.codec, self.xeth, self.ethereum)
 	if err != nil {
 		return false, err
 	}
@@ -439,7 +434,7 @@ func (self *adminApi) GetContractInfo(req *shared.Request) (interface{}, error)
 		return nil, shared.NewDecodeParamError(err.Error())
 	}
 
-	infoDoc, err := natspec.FetchDocsForContract(args.Contract, self.xeth, self.ds)
+	infoDoc, err := natspec.FetchDocsForContract(args.Contract, self.xeth, self.ethereum.HTTPClient())
 	if err != nil {
 		return nil, err
 	}
@@ -459,7 +454,7 @@ func (self *adminApi) HttpGet(req *shared.Request) (interface{}, error) {
 		return nil, shared.NewDecodeParamError(err.Error())
 	}
 
-	resp, err := self.ds.Get(args.Uri, args.Path)
+	resp, err := self.ethereum.HTTPClient().Get(args.Uri, args.Path)
 	if err != nil {
 		return nil, err
 	}
diff --git a/rpc/api/api_test.go b/rpc/api/api_test.go
index a4efb09c1..131ef68f8 100644
--- a/rpc/api/api_test.go
+++ b/rpc/api/api_test.go
@@ -30,7 +30,7 @@ import (
 )
 
 func TestParseApiString(t *testing.T) {
-	apis, err := ParseApiString("", codec.JSON, nil, nil, "")
+	apis, err := ParseApiString("", codec.JSON, nil, nil)
 	if err == nil {
 		t.Errorf("Expected an err from parsing empty API string but got nil")
 	}
@@ -39,7 +39,7 @@ func TestParseApiString(t *testing.T) {
 		t.Errorf("Expected 0 apis from empty API string")
 	}
 
-	apis, err = ParseApiString("eth", codec.JSON, nil, nil, "")
+	apis, err = ParseApiString("eth", codec.JSON, nil, nil)
 	if err != nil {
 		t.Errorf("Expected nil err from parsing empty API string but got %v", err)
 	}
@@ -48,7 +48,7 @@ func TestParseApiString(t *testing.T) {
 		t.Errorf("Expected 1 apis but got %d - %v", apis, apis)
 	}
 
-	apis, err = ParseApiString("eth,eth", codec.JSON, nil, nil, "")
+	apis, err = ParseApiString("eth,eth", codec.JSON, nil, nil)
 	if err != nil {
 		t.Errorf("Expected nil err from parsing empty API string but got \"%v\"", err)
 	}
@@ -57,7 +57,7 @@ func TestParseApiString(t *testing.T) {
 		t.Errorf("Expected 2 apis but got %d - %v", apis, apis)
 	}
 
-	apis, err = ParseApiString("eth,invalid", codec.JSON, nil, nil, "")
+	apis, err = ParseApiString("eth,invalid", codec.JSON, nil, nil)
 	if err == nil {
 		t.Errorf("Expected an err but got no err")
 	}
diff --git a/rpc/api/eth.go b/rpc/api/eth.go
index 4722682ff..b84ae31da 100644
--- a/rpc/api/eth.go
+++ b/rpc/api/eth.go
@@ -24,6 +24,7 @@ import (
 	"fmt"
 
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/natspec"
 	"github.com/ethereum/go-ethereum/eth"
 	"github.com/ethereum/go-ethereum/rpc/codec"
 	"github.com/ethereum/go-ethereum/rpc/shared"
@@ -67,6 +68,7 @@ var (
 		"eth_getUncleCountByBlockNumber":          (*ethApi).GetUncleCountByBlockNumber,
 		"eth_getData":                             (*ethApi).GetData,
 		"eth_getCode":                             (*ethApi).GetData,
+		"eth_getNatSpec":                          (*ethApi).GetNatSpec,
 		"eth_sign":                                (*ethApi).Sign,
 		"eth_sendRawTransaction":                  (*ethApi).SendRawTransaction,
 		"eth_sendTransaction":                     (*ethApi).SendTransaction,
@@ -322,6 +324,18 @@ func (self *ethApi) SendTransaction(req *shared.Request) (interface{}, error) {
 	return v, nil
 }
 
+func (self *ethApi) GetNatSpec(req *shared.Request) (interface{}, error) {
+	args := new(NewTxArgs)
+	if err := self.codec.Decode(req.Params, &args); err != nil {
+		return nil, shared.NewDecodeParamError(err.Error())
+	}
+
+	var jsontx = fmt.Sprintf(`{"params":[{"to":"%s","data": "%s"}]}`, args.To, args.Data)
+	notice := natspec.GetNotice(self.xeth, jsontx, self.ethereum.HTTPClient())
+
+	return notice, nil
+}
+
 func (self *ethApi) EstimateGas(req *shared.Request) (interface{}, error) {
 	_, gas, err := self.doCall(req.Params)
 	if err != nil {
diff --git a/rpc/api/eth_js.go b/rpc/api/eth_js.go
index 393dac22f..75c103c9d 100644
--- a/rpc/api/eth_js.go
+++ b/rpc/api/eth_js.go
@@ -35,6 +35,12 @@ web3._extend({
 			call: 'eth_resend',
 			params: 3,
 			inputFormatter: [web3._extend.formatters.inputTransactionFormatter, web3._extend.utils.fromDecimal, web3._extend.utils.fromDecimal]
+		}),
+    new web3._extend.Method({
+	    name: 'getNatSpec',
+	    call: 'eth_getNatSpec',
+	    params: 1,
+	    inputFormatter: [web3._extend.formatters.inputTransactionFormatter]
 		})
 	],
 	properties:
diff --git a/rpc/api/utils.go b/rpc/api/utils.go
index 719cb8074..5a3ade46b 100644
--- a/rpc/api/utils.go
+++ b/rpc/api/utils.go
@@ -89,6 +89,7 @@ var (
 			"getBlockTransactionCount",
 			"getBlockUncleCount",
 			"getCode",
+			"getNatSpec",
 			"getCompilers",
 			"gasPrice",
 			"getStorageAt",
@@ -153,7 +154,7 @@ var (
 )
 
 // Parse a comma separated API string to individual api's
-func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.Ethereum, docRoot string) ([]shared.EthereumApi, error) {
+func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.Ethereum) ([]shared.EthereumApi, error) {
 	if len(strings.TrimSpace(apistr)) == 0 {
 		return nil, fmt.Errorf("Empty apistr provided")
 	}
@@ -164,7 +165,7 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, eth *eth.
 	for i, name := range names {
 		switch strings.ToLower(strings.TrimSpace(name)) {
 		case shared.AdminApiName:
-			apis[i] = NewAdminApi(xeth, eth, codec, docRoot)
+			apis[i] = NewAdminApi(xeth, eth, codec)
 		case shared.DebugApiName:
 			apis[i] = NewDebugApi(xeth, eth, codec)
 		case shared.DbApiName:
-- 
cgit v1.2.3