Bläddra i källkod

Merge branch 'frontier/natspec' of https://github.com/ethersphere/go-ethereum into ethersphere-frontier/natspec

obscuren 10 år sedan
förälder
incheckning
36ec42e50c

+ 13 - 5
cmd/geth/js.go

@@ -25,7 +25,8 @@ import (
 	"strings"
 
 	"github.com/ethereum/go-ethereum/cmd/utils"
-	"github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/common/docserver"
+	"github.com/ethereum/go-ethereum/common/natspec"
 	"github.com/ethereum/go-ethereum/eth"
 	re "github.com/ethereum/go-ethereum/jsre"
 	"github.com/ethereum/go-ethereum/rpc"
@@ -139,10 +140,17 @@ var net = web3.net;
 	js.re.Eval(globalRegistrar + "registrar = new GlobalRegistrar(\"" + globalRegistrarAddr + "\");")
 }
 
-func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool {
-	p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx)
-	answer, _ := self.Prompt(p)
-	return strings.HasPrefix(strings.Trim(answer, " "), "y")
+var ds, _ = docserver.New(utils.JSpathFlag.String())
+
+func (self *jsre) ConfirmTransaction(tx string) bool {
+	if self.ethereum.NatSpec {
+		notice := natspec.GetNotice(self.xeth, tx, ds)
+		fmt.Println(notice)
+		answer, _ := self.Prompt("Confirm Transaction\n[y/n] ")
+		return strings.HasPrefix(strings.Trim(answer, " "), "y")
+	} else {
+		return true
+	}
 }
 
 func (self *jsre) UnlockAccount(addr []byte) bool {

+ 1 - 0
cmd/geth/main.go

@@ -231,6 +231,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
 		utils.MinerThreadsFlag,
 		utils.MiningEnabledFlag,
 		utils.NATFlag,
+		utils.NatspecEnabledFlag,
 		utils.NodeKeyFileFlag,
 		utils.NodeKeyHexFlag,
 		utils.RPCEnabledFlag,

+ 5 - 0
cmd/utils/flags.go

@@ -93,6 +93,10 @@ var (
 		Name:  "identity",
 		Usage: "node name",
 	}
+	NatspecEnabledFlag = cli.BoolFlag{
+		Name:  "natspec",
+		Usage: "Enable NatSpec confirmation notice",
+	}
 
 	// miner settings
 	MinerThreadsFlag = cli.IntFlag{
@@ -268,6 +272,7 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config {
 		MaxPeers:           ctx.GlobalInt(MaxPeersFlag.Name),
 		Port:               ctx.GlobalString(ListenPortFlag.Name),
 		NAT:                GetNAT(ctx),
+		NatSpec:            ctx.GlobalBool(NatspecEnabledFlag.Name),
 		NodeKey:            GetNodeKey(ctx),
 		Shh:                true,
 		Dial:               true,

+ 17 - 0
common/bytes.go

@@ -147,6 +147,23 @@ func Hex2Bytes(str string) []byte {
 	return h
 }
 
+func Hex2BytesFixed(str string, flen int) []byte {
+
+	h, _ := hex.DecodeString(str)
+	if len(h) == flen {
+		return h
+	} else {
+		if len(h) > flen {
+			return h[len(h)-flen : len(h)]
+		} else {
+			hh := make([]byte, flen)
+			copy(hh[flen-len(h):flen], h[:])
+			return hh
+		}
+	}
+
+}
+
 func StringToByteFunc(str string, cb func(str string) []byte) (ret []byte) {
 	if len(str) > 1 && str[0:2] == "0x" && !strings.Contains(str, "\n") {
 		ret = Hex2Bytes(str[2:])

+ 82 - 0
common/docserver/docserver.go

@@ -0,0 +1,82 @@
+package docserver
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+// http://golang.org/pkg/net/http/#RoundTripper
+var (
+	schemes = map[string]func(*DocServer) http.RoundTripper{
+		// Simple File server from local disk file:///etc/passwd :)
+		"file": fileServerOnDocRoot,
+	}
+)
+
+func fileServerOnDocRoot(ds *DocServer) http.RoundTripper {
+	return http.NewFileTransport(http.Dir(ds.DocRoot))
+}
+
+type DocServer struct {
+	*http.Transport
+	DocRoot string
+}
+
+func New(docRoot string) (self *DocServer, err error) {
+	self = &DocServer{
+		Transport: &http.Transport{},
+		DocRoot:   docRoot,
+	}
+	err = self.RegisterProtocols(schemes)
+	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) RegisterProtocols(schemes map[string]func(*DocServer) http.RoundTripper) (err error) {
+	for scheme, rtf := range schemes {
+		self.RegisterProtocol(scheme, rtf(self))
+	}
+	return
+}
+
+func (self *DocServer) GetAuthContent(uri string, hash common.Hash) (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
+	}
+
+	// check hash to authenticate content
+	hashbytes := crypto.Sha3(content)
+	var chash common.Hash
+	copy(chash[:], hashbytes)
+	if chash != hash {
+		content = nil
+		err = fmt.Errorf("content hash mismatch")
+	}
+
+	return
+
+}

+ 38 - 0
common/docserver/docserver_test.go

@@ -0,0 +1,38 @@
+package docserver
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+func TestGetAuthContent(t *testing.T) {
+	text := "test"
+	hash := common.Hash{}
+	copy(hash[:], crypto.Sha3([]byte(text)))
+	ioutil.WriteFile("/tmp/test.content", []byte(text), os.ModePerm)
+
+	ds, err := New("/tmp/")
+	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"
+	if err == nil {
+		t.Errorf("expected error, got nothing")
+	} else {
+		if err.Error() != expected {
+			t.Errorf("expected error '%s' got '%v'", expected, err)
+		}
+	}
+
+}

+ 185 - 10
common/natspec/natspec.go

@@ -1,20 +1,137 @@
 package natspec
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"github.com/robertkrimen/otto"
+	"strings"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/docserver"
+	"github.com/ethereum/go-ethereum/common/resolver"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/xeth"
 )
 
+type abi2method map[[8]byte]*method
+
 type NatSpec struct {
-	jsvm *otto.Otto
+	jsvm                    *otto.Otto
+	userDocJson, abiDocJson []byte
+	userDoc                 userDoc
+	tx, data                string
+	// abiDoc abiDoc
+}
+
+func getFallbackNotice(comment, tx string) string {
+
+	return "About to submit transaction (" + comment + "): " + tx
+
 }
 
-// TODO: should initialise with abi and userdoc jsons
-func New() (self *NatSpec, err error) {
+func GetNotice(xeth *xeth.XEth, tx string, http *docserver.DocServer) (notice string) {
+
+	ns, err := New(xeth, tx, http)
+	if err != nil {
+		if ns == nil {
+			return getFallbackNotice("no NatSpec info found for contract", tx)
+		} else {
+			return getFallbackNotice("invalid NatSpec info", tx)
+		}
+	}
+
+	notice, err2 := ns.Notice()
+
+	if err2 != nil {
+		return getFallbackNotice("NatSpec notice error \""+err2.Error()+"\"", tx)
+	}
+
+	return
+
+}
+
+func New(xeth *xeth.XEth, tx string, http *docserver.DocServer) (self *NatSpec, err error) {
+
+	// extract contract address from tx
+
+	var obj map[string]json.RawMessage
+	err = json.Unmarshal([]byte(tx), &obj)
+	if err != nil {
+		return
+	}
+	var tmp []map[string]string
+	err = json.Unmarshal(obj["params"], &tmp)
+	if err != nil {
+		return
+	}
+	contractAddress := tmp[0]["to"]
 
-	self = new(NatSpec)
-	self.jsvm = otto.New()
+	// retrieve contract hash from state
+	if !xeth.IsContract(contractAddress) {
+		err = fmt.Errorf("NatSpec error: contract not found")
+		return
+	}
+	codehex := xeth.CodeAt(contractAddress)
+	codeHash := common.BytesToHash(crypto.Sha3(common.Hex2Bytes(codehex[2:])))
+	// parse out host/domain
 
+	// set up nameresolver with natspecreg + urlhint contract addresses
+	res := resolver.New(
+		xeth,
+		resolver.URLHintContractAddress,
+		resolver.HashRegContractAddress,
+	)
+
+	// resolve host via HashReg/UrlHint Resolver
+	uri, hash, err := res.KeyToUrl(codeHash)
+	if err != nil {
+		return
+	}
+
+	// get content via http client and authenticate content using hash
+	content, err := http.GetAuthContent(uri, hash)
+	if err != nil {
+		return
+	}
+
+	// get abi, userdoc
+	var obj2 map[string]json.RawMessage
+	err = json.Unmarshal(content, &obj2)
+	if err != nil {
+		return
+	}
+
+	abi := []byte(obj2["abi"])
+	userdoc := []byte(obj2["userdoc"])
+
+	self, err = NewWithDocs(abi, userdoc, tx)
+	return
+}
+
+func NewWithDocs(abiDocJson, userDocJson []byte, tx string) (self *NatSpec, err error) {
+
+	var obj map[string]json.RawMessage
+	err = json.Unmarshal([]byte(tx), &obj)
+	if err != nil {
+		return
+	}
+	var tmp []map[string]string
+	err = json.Unmarshal(obj["params"], &tmp)
+	if err != nil {
+		return
+	}
+	data := tmp[0]["data"]
+
+	self = &NatSpec{
+		jsvm:        otto.New(),
+		abiDocJson:  abiDocJson,
+		userDocJson: userDocJson,
+		tx:          tx,
+		data:        data,
+	}
+
+	// load and require natspec js (but it is meant to be protected environment)
 	_, err = self.jsvm.Run(natspecJS)
 	if err != nil {
 		return
@@ -24,20 +141,78 @@ func New() (self *NatSpec, err error) {
 		return
 	}
 
+	err = json.Unmarshal(userDocJson, &self.userDoc)
+	// err = parseAbiJson(abiDocJson, &self.abiDoc)
+
 	return
 }
 
-func (self *NatSpec) Notice(transaction, abi, method, expression string) (string, error) {
-	var err error
-	if _, err = self.jsvm.Run("var transaction = " + transaction + ";"); err != nil {
+// type abiDoc []method
+
+// type method struct {
+// 	Name   string  `json:name`
+// 	Inputs []input `json:inputs`
+// 	abiKey [8]byte
+// }
+
+// type input struct {
+// 	Name string `json:name`
+// 	Type string `json:type`
+// }
+
+// json skeleton for abi doc (contract method definitions)
+type method struct {
+	Notice string `json:notice`
+	name   string
+}
+
+type userDoc struct {
+	Methods map[string]*method `json:methods`
+}
+
+func (self *NatSpec) makeAbi2method(abiKey [8]byte) (meth *method) {
+	for signature, m := range self.userDoc.Methods {
+		name := strings.Split(signature, "(")[0]
+		hash := []byte(common.Bytes2Hex(crypto.Sha3([]byte(signature))))
+		var key [8]byte
+		copy(key[:], hash[:8])
+		if bytes.Equal(key[:], abiKey[:]) {
+			meth = m
+			meth.name = name
+			return
+		}
+	}
+	return
+}
+
+func (self *NatSpec) Notice() (notice string, err error) {
+	var abiKey [8]byte
+	if len(self.data) < 10 {
+		err = fmt.Errorf("Invalid transaction data")
+		return
+	}
+	copy(abiKey[:], self.data[2:10])
+	meth := self.makeAbi2method(abiKey)
+
+	if meth == nil {
+		err = fmt.Errorf("abi key does not match any method")
+		return
+	}
+	notice, err = self.noticeForMethod(self.tx, meth.name, meth.Notice)
+	return
+}
+
+func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice string, err error) {
+
+	if _, err = self.jsvm.Run("var transaction = " + tx + ";"); err != nil {
 		return "", fmt.Errorf("natspec.js error setting transaction: %v", err)
 	}
 
-	if _, err = self.jsvm.Run("var abi = " + abi + ";"); err != nil {
+	if _, err = self.jsvm.Run("var abi = " + string(self.abiDocJson) + ";"); err != nil {
 		return "", fmt.Errorf("natspec.js error setting abi: %v", err)
 	}
 
-	if _, err = self.jsvm.Run("var method = '" + method + "';"); err != nil {
+	if _, err = self.jsvm.Run("var method = '" + name + "';"); err != nil {
 		return "", fmt.Errorf("natspec.js error setting method: %v", err)
 	}
 

+ 340 - 0
common/natspec/natspec_e2e_test.go

@@ -0,0 +1,340 @@
+package natspec
+
+import (
+	"io/ioutil"
+	"math/big"
+	"os"
+	"testing"
+
+	"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/resolver"
+	"github.com/ethereum/go-ethereum/core"
+	"github.com/ethereum/go-ethereum/core/state"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/ethereum/go-ethereum/eth"
+	"github.com/ethereum/go-ethereum/rpc"
+	xe "github.com/ethereum/go-ethereum/xeth"
+)
+
+type testFrontend struct {
+	t           *testing.T
+	ethereum    *eth.Ethereum
+	xeth        *xe.XEth
+	api         *rpc.EthereumApi
+	coinbase    string
+	stateDb     *state.StateDB
+	txc         uint64
+	lastConfirm string
+	makeNatSpec bool
+}
+
+const (
+	testAccount = "e273f01c99144c438695e10f24926dc1f9fbf62d"
+	testBalance = "1000000000000"
+)
+
+const testFileName = "long_file_name_for_testing_registration_of_URLs_longer_than_32_bytes.content"
+
+const testNotice = "Register key `utils.toHex(_key)` <- content `utils.toHex(_content)`"
+const testExpNotice = "Register key 0xadd1a7d961cff0242089674ec2ef6fca671ab15e1fe80e38859fc815b98d88ab <- content 0xc00d5bcc872e17813df6ec5c646bb281a6e2d3b454c2c400c78192adf3344af9"
+const testExpNotice2 = `About to submit transaction (NatSpec notice error "abi key does not match any method"): {"id":6,"jsonrpc":"2.0","method":"eth_transact","params":[{"from":"0xe273f01c99144c438695e10f24926dc1f9fbf62d","to":"0xb737b91f8e95cf756766fc7c62c9a8ff58470381","value":"100000000000","gas":"100000","gasPrice":"100000","data":"0x31e12c20"}]}`
+const testExpNotice3 = `About to submit transaction (no NatSpec info found for contract): {"id":6,"jsonrpc":"2.0","method":"eth_transact","params":[{"from":"0xe273f01c99144c438695e10f24926dc1f9fbf62d","to":"0x8b839ad85686967a4f418eccc81962eaee314ac3","value":"100000000000","gas":"100000","gasPrice":"100000","data":"0x300a3bbfc00d5bcc872e17813df6ec5c646bb281a6e2d3b454c2c400c78192adf3344af900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"}]}`
+
+const testUserDoc = `
+{
+  "source": "...",
+  "language": "Solidity",
+  "languageVersion": 1,
+  "methods": {
+    "register(uint256,uint256)": {
+      "notice":  "` + testNotice + `"
+    }
+  },
+  "invariants": [
+    { "notice": "" }
+  ],
+  "construction": [
+    { "notice": "" }
+  ]
+}
+`
+
+const testABI = `
+[{
+  "name": "register",
+  "constant": false,
+  "type": "function",
+  "inputs": [{
+    "name": "_key",
+    "type": "uint256"
+  }, {
+    "name": "_content",
+    "type": "uint256"
+  }],
+  "outputs": []
+}]
+`
+
+const testDocs = `
+{
+	"userdoc": ` + testUserDoc + `,
+	"abi": ` + testABI + `
+}
+`
+
+func (f *testFrontend) UnlockAccount(acc []byte) bool {
+	f.t.Logf("Unlocking account %v\n", common.Bytes2Hex(acc))
+	f.ethereum.AccountManager().Unlock(acc, "password")
+	return true
+}
+
+func (f *testFrontend) ConfirmTransaction(tx string) bool {
+	//f.t.Logf("ConfirmTransaction called  tx = %v", tx)
+	if f.makeNatSpec {
+		ds, err := docserver.New("/tmp/")
+		if err != nil {
+			f.t.Errorf("Error creating DocServer: %v", err)
+		}
+		f.lastConfirm = GetNotice(f.xeth, tx, ds)
+	}
+	return true
+}
+
+var port = 30300
+
+func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) {
+	os.RemoveAll("/tmp/eth-natspec/")
+	err = os.MkdirAll("/tmp/eth-natspec/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/", os.ModePerm)
+	if err != nil {
+		t.Errorf("%v", err)
+		return
+	}
+	err = os.MkdirAll("/tmp/eth-natspec/data", os.ModePerm)
+	if err != nil {
+		t.Errorf("%v", err)
+		return
+	}
+	ks := crypto.NewKeyStorePlain("/tmp/eth-natspec/keys")
+	ioutil.WriteFile("/tmp/eth-natspec/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/e273f01c99144c438695e10f24926dc1f9fbf62d",
+		[]byte(`{"Id":"RhRXD+fNRKS4jx+7ZfEsNA==","Address":"4nPwHJkUTEOGleEPJJJtwfn79i0=","PrivateKey":"h4ACVpe74uIvi5Cg/2tX/Yrm2xdr3J7QoMbMtNX2CNc="}`), os.ModePerm)
+
+	port++
+	ethereum, err = eth.New(&eth.Config{
+		DataDir:        "/tmp/eth-natspec",
+		AccountManager: accounts.NewManager(ks),
+		Name:           "test",
+	})
+
+	if err != nil {
+		t.Errorf("%v", err)
+		return
+	}
+
+	return
+}
+
+func testInit(t *testing.T) (self *testFrontend) {
+
+	core.GenesisData = []byte(`{
+	"` + testAccount + `": {"balance": "` + testBalance + `"}
+	}`)
+
+	ethereum, err := testEth(t)
+	if err != nil {
+		t.Errorf("error creating ethereum: %v", err)
+		return
+	}
+	err = ethereum.Start()
+	if err != nil {
+		t.Errorf("error starting ethereum: %v", err)
+		return
+	}
+
+	self = &testFrontend{t: t, ethereum: ethereum}
+	self.xeth = xe.New(ethereum, self)
+	self.api = rpc.NewEthereumApi(self.xeth)
+
+	addr := self.xeth.Coinbase()
+	self.coinbase = addr
+	if addr != "0x"+testAccount {
+		t.Errorf("CoinBase %v does not match TestAccount 0x%v", addr, testAccount)
+	}
+	t.Logf("CoinBase is %v", addr)
+
+	balance := self.xeth.BalanceAt(testAccount)
+	/*if balance != core.TestBalance {
+		t.Errorf("Balance %v does not match TestBalance %v", balance, core.TestBalance)
+	}*/
+	t.Logf("Balance is %v", balance)
+
+	self.stateDb = self.ethereum.ChainManager().State().Copy()
+
+	return
+
+}
+
+func (self *testFrontend) insertTx(addr, contract, fnsig string, args []string) {
+
+	//cb := common.HexToAddress(self.coinbase)
+	//coinbase := self.ethereum.ChainManager().State().GetStateObject(cb)
+
+	hash := common.Bytes2Hex(crypto.Sha3([]byte(fnsig)))
+	data := "0x" + hash[0:8]
+	for _, arg := range args {
+		data = data + common.Bytes2Hex(common.Hex2BytesFixed(arg, 32))
+	}
+	self.t.Logf("Tx data: %v", data)
+
+	jsontx := `
+[{
+	  "from": "` + addr + `",
+      "to": "` + contract + `",
+	  "value": "100000000000",
+	  "gas": "100000",
+	  "gasPrice": "100000",
+      "data": "` + data + `"
+}]
+`
+	req := &rpc.RpcRequest{
+		Jsonrpc: "2.0",
+		Method:  "eth_transact",
+		Params:  []byte(jsontx),
+		Id:      6,
+	}
+
+	var reply interface{}
+	err0 := self.api.GetRequestReply(req, &reply)
+	if err0 != nil {
+		self.t.Errorf("GetRequestReply error: %v", err0)
+	}
+
+	//self.xeth.Transact(addr, contract, "100000000000", "100000", "100000", data)
+
+}
+
+func (self *testFrontend) applyTxs() {
+
+	cb := common.HexToAddress(self.coinbase)
+	block := self.ethereum.ChainManager().NewBlock(cb)
+	coinbase := self.stateDb.GetStateObject(cb)
+	coinbase.SetGasPool(big.NewInt(10000000))
+	txs := self.ethereum.TxPool().GetTransactions()
+
+	for i := 0; i < len(txs); i++ {
+		for _, tx := range txs {
+			//self.t.Logf("%v %v %v", i, tx.Nonce(), self.txc)
+			if tx.Nonce() == self.txc {
+				_, gas, err := core.ApplyMessage(core.NewEnv(self.stateDb, self.ethereum.ChainManager(), tx, block), tx, coinbase)
+				//self.ethereum.TxPool().RemoveSet([]*types.Transaction{tx})
+				self.t.Logf("ApplyMessage: gas %v  err %v", gas, err)
+				self.txc++
+			}
+		}
+	}
+
+	//self.ethereum.TxPool().RemoveSet(txs)
+	self.xeth = self.xeth.WithState(self.stateDb)
+
+}
+
+func (self *testFrontend) registerURL(hash common.Hash, url string) {
+	hashHex := common.Bytes2Hex(hash[:])
+	urlBytes := []byte(url)
+	var bb bool = true
+	var cnt byte
+	for bb {
+		bb = len(urlBytes) > 0
+		urlb := urlBytes
+		if len(urlb) > 32 {
+			urlb = urlb[:32]
+		}
+		urlHex := common.Bytes2Hex(urlb)
+		self.insertTx(self.coinbase, resolver.URLHintContractAddress, "register(uint256,uint8,uint256)", []string{hashHex, common.Bytes2Hex([]byte{cnt}), urlHex})
+		if len(urlBytes) > 32 {
+			urlBytes = urlBytes[32:]
+		} else {
+			urlBytes = nil
+		}
+		cnt++
+	}
+}
+
+func (self *testFrontend) setOwner() {
+
+	self.insertTx(self.coinbase, resolver.HashRegContractAddress, "setowner()", []string{})
+
+	/*owner := self.xeth.StorageAt("0x"+resolver.HashRegContractAddress, "0x0000000000000000000000000000000000000000000000000000000000000000")
+	self.t.Logf("owner = %v", owner)
+	if owner != self.coinbase {
+		self.t.Errorf("setowner() unsuccessful, owner != coinbase")
+	}*/
+}
+
+func (self *testFrontend) registerNatSpec(codehash, dochash common.Hash) {
+
+	codeHex := common.Bytes2Hex(codehash[:])
+	docHex := common.Bytes2Hex(dochash[:])
+	self.insertTx(self.coinbase, resolver.HashRegContractAddress, "register(uint256,uint256)", []string{codeHex, docHex})
+}
+
+func (self *testFrontend) testResolver() *resolver.Resolver {
+	return resolver.New(self.xeth, resolver.URLHintContractAddress, resolver.HashRegContractAddress)
+}
+
+func TestNatspecE2E(t *testing.T) {
+
+	tf := testInit(t)
+	defer tf.ethereum.Stop()
+
+	resolver.CreateContracts(tf.xeth, testAccount)
+	t.Logf("URLHint contract registered at %v", resolver.URLHintContractAddress)
+	t.Logf("HashReg contract registered at %v", resolver.HashRegContractAddress)
+	tf.applyTxs()
+
+	ioutil.WriteFile("/tmp/"+testFileName, []byte(testDocs), os.ModePerm)
+	dochash := common.BytesToHash(crypto.Sha3([]byte(testDocs)))
+
+	codehex := tf.xeth.CodeAt(resolver.HashRegContractAddress)
+	codehash := common.BytesToHash(crypto.Sha3(common.Hex2Bytes(codehex[2:])))
+
+	tf.setOwner()
+	tf.registerNatSpec(codehash, dochash)
+	tf.registerURL(dochash, "file:///"+testFileName)
+	tf.applyTxs()
+
+	chash, err := tf.testResolver().KeyToContentHash(codehash)
+	if err != nil {
+		t.Errorf("Can't find content hash")
+	}
+	t.Logf("chash = %x  err = %v", chash, err)
+	url, err2 := tf.testResolver().ContentHashToUrl(dochash)
+	if err2 != nil {
+		t.Errorf("Can't find URL hint")
+	}
+	t.Logf("url = %v  err = %v", url, err2)
+
+	// NatSpec info for register method of HashReg contract installed
+	// now using the same transactions to check confirm messages
+
+	tf.makeNatSpec = true
+	tf.registerNatSpec(codehash, dochash)
+	t.Logf("Confirm message: %v\n", tf.lastConfirm)
+	if tf.lastConfirm != testExpNotice {
+		t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice, tf.lastConfirm)
+	}
+
+	tf.setOwner()
+	t.Logf("Confirm message for unknown method: %v\n", tf.lastConfirm)
+	if tf.lastConfirm != testExpNotice2 {
+		t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice2, tf.lastConfirm)
+	}
+
+	tf.registerURL(dochash, "file:///test.content")
+	t.Logf("Confirm message for unknown contract: %v\n", tf.lastConfirm)
+	if tf.lastConfirm != testExpNotice3 {
+		t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice3, tf.lastConfirm)
+	}
+
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
common/natspec/natspec_js.go


+ 98 - 44
common/natspec/natspec_test.go

@@ -4,87 +4,141 @@ import (
 	"testing"
 )
 
-func TestNotice(t *testing.T) {
+func makeUserdoc(desc string) []byte {
+	return []byte(`
+{
+  "source": "...",
+  "language": "Solidity",
+  "languageVersion": 1,
+  "methods": {
+    "multiply(uint256)": {
+      "notice":  "` + desc + `"
+    },
+    "balance(address)": {
+      "notice": "` + "`(balanceInmGAV / 1000).fixed(0,3)`" + ` GAV is the total funds available to ` + "`who.address()`." + `"
+    }
+  },
+  "invariants": [
+    { "notice": "The sum total amount of GAV in the system is 1 million." }
+  ],
+  "construction": [
+    { "notice": "Endows ` + "`message.caller.address()`" + ` with 1m GAV." }
+  ]
+}
+`)
+}
 
-	tx := `
-	{
-    "jsonrpc": "2.0",
-    "method": "eth_call",
-    "params": [{
-        "to": "0x8521742d3f456bd237e312d6e30724960f72517a",
-        "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a"
-    }],
-    "id": 6
-  }
-	`
-
-	abi := `
-	[{
-    "name": "multiply",
-    "constant": false,
-    "type": "function",
-    "inputs": [{
-      "name": "a",
-      "type": "uint256"
-    }],
-    "outputs": [{
-      "name": "d",
-      "type": "uint256"
-    }]
+var data = "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a"
+
+var tx = `
+{
+  "jsonrpc": "2.0",
+  "method": "eth_call",
+  "params": [{
+      "to": "0x8521742d3f456bd237e312d6e30724960f72517a",
+      "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a"
+  }],
+  "id": 6
+}
+`
+
+var abi = []byte(`
+[{
+  "name": "multiply",
+  "constant": false,
+  "type": "function",
+  "inputs": [{
+    "name": "a",
+    "type": "uint256"
+  }],
+  "outputs": [{
+    "name": "d",
+    "type": "uint256"
   }]
-	`
+}]
+`)
+
+func TestNotice(t *testing.T) {
 
 	desc := "Will multiply `a` by 7 and return `a * 7`."
+	expected := "Will multiply 122 by 7 and return 854."
 
-	method := "multiply"
+	userdoc := makeUserdoc(desc)
 
-	ns, err := New()
+	ns, err := NewWithDocs(abi, userdoc, tx)
 	if err != nil {
-		t.Errorf("NewNATSpec error %v", err)
+		t.Errorf("New: error: %v", err)
 	}
 
-	notice, err := ns.Notice(tx, abi, method, desc)
+	notice, err := ns.Notice()
 
 	if err != nil {
-		t.Errorf("expected no error got %v", err)
+		t.Errorf("expected no error, got %v", err)
 	}
 
-	expected := "Will multiply 122 by 7 and return 854."
 	if notice != expected {
 		t.Errorf("incorrect notice. expected %v, got %v", expected, notice)
 	} else {
 		t.Logf("returned notice \"%v\"", notice)
 	}
+}
+
+// test missing method
+func TestMissingMethod(t *testing.T) {
 
-	notice, err = ns.Notice(tx, abi, method, "Will multiply 122 by \"7\" and return 854.")
+	desc := "Will multiply `a` by 7 and return `a * 7`."
+	userdoc := makeUserdoc(desc)
+	expected := "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist"
+
+	ns, err := NewWithDocs(abi, userdoc, tx)
+	if err != nil {
+		t.Errorf("New: error: %v", err)
+	}
 
-	expected = "natspec.js error setting expression: (anonymous): Line 1:41 Unexpected number"
+	notice, err := ns.noticeForMethod(tx, "missing_method", "")
 
 	if err == nil {
-		t.Errorf("expected error, got nothing (notice: '%v')", err, notice)
+		t.Errorf("expected error, got nothing (notice: '%v')", notice)
 	} else {
 		if err.Error() != expected {
 			t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice)
 		}
 	}
+}
+
+// test invalid desc
 
-	// https://github.com/ethereum/natspec.js/issues/1
-	badDesc := "Will multiply `e` by 7 and return `a * 7`."
-	notice, err = ns.Notice(tx, abi, method, badDesc)
+func TestInvalidDesc(t *testing.T) {
 
-	expected = "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params"
+	desc := "Will multiply 122 by \"7\" and return 854."
+	expected := "invalid character '7' after object key:value pair"
 
+	userdoc := makeUserdoc(desc)
+
+	_, err := NewWithDocs(abi, userdoc, tx)
 	if err == nil {
-		t.Errorf("expected error, got nothing (notice: '%v')", notice)
+		t.Errorf("expected error, got nothing", err)
 	} else {
 		if err.Error() != expected {
-			t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice)
+			t.Errorf("expected error '%s' got '%v'", expected, err)
 		}
 	}
+}
+
+// test wrong input params
+func TestWrongInputParams(t *testing.T) {
+
+	desc := "Will multiply `e` by 7 and return `a * 7`."
+	expected := "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params"
 
-	notice, err = ns.Notice(tx, abi, "missing_method", desc)
+	userdoc := makeUserdoc(desc)
+
+	ns, err := NewWithDocs(abi, userdoc, tx)
+	if err != nil {
+		t.Errorf("New: error: %v", err)
+	}
 
-	expected = "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist"
+	notice, err := ns.Notice()
 
 	if err == nil {
 		t.Errorf("expected error, got nothing (notice: '%v')", notice)

+ 36 - 0
common/resolver/contracts.go

@@ -0,0 +1,36 @@
+package resolver
+
+const ( // built-in contracts address and code
+	ContractCodeURLhint = "0x60c180600c6000396000f30060003560e060020a90048063300a3bbf14601557005b6024600435602435604435602a565b60006000f35b6000600084815260200190815260200160002054600160a060020a0316600014806078575033600160a060020a03166000600085815260200190815260200160002054600160a060020a0316145b607f5760bc565b336000600085815260200190815260200160002081905550806001600085815260200190815260200160002083610100811060b657005b01819055505b50505056"
+	/*
+		contract URLhint {
+			function register(uint256 _hash, uint8 idx, uint256 _url) {
+				if (owner[_hash] == 0 || owner[_hash] == msg.sender) {
+					owner[_hash] = msg.sender;
+					url[_hash][idx] = _url;
+				}
+			}
+			mapping (uint256 => address) owner;
+			mapping (uint256 => uint256[256]) url;
+		}
+	*/
+
+	ContractCodeHashReg = "0x609880600c6000396000f30060003560e060020a9004806331e12c2014601f578063d66d6c1014602b57005b6025603d565b60006000f35b6037600435602435605d565b60006000f35b600054600160a060020a0316600014605357605b565b336000819055505b565b600054600160a060020a031633600160a060020a031614607b576094565b8060016000848152602001908152602001600020819055505b505056"
+	/*
+		   contract HashReg {
+			function setowner() {
+				if (owner == 0) {
+					owner = msg.sender;
+				}
+			}
+		   	function register(uint256 _key, uint256 _content) {
+				if (msg.sender == owner) {
+		   			content[_key] = _content;
+				}
+		   	}
+			address owner;
+		   	mapping (uint256 => uint256) content;
+		   }
+	*/
+
+)

+ 128 - 0
common/resolver/resolver.go

@@ -0,0 +1,128 @@
+package resolver
+
+import (
+	"encoding/binary"
+	"fmt"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+	xe "github.com/ethereum/go-ethereum/xeth"
+)
+
+/*
+Resolver implements the Ethereum DNS mapping
+HashReg : Key Hash (hash of domain name or contract code) -> Content Hash
+UrlHint : Content Hash -> Url Hint
+
+The resolver is meant to be called by the roundtripper transport implementation
+of a url scheme
+*/
+
+// contract addresses will be hardcoded after they're created
+var URLHintContractAddress string = "0000000000000000000000000000000000000000000000000000000000001234"
+var HashRegContractAddress string = "0000000000000000000000000000000000000000000000000000000000005678"
+
+func CreateContracts(xeth *xe.XEth, addr string) {
+	var err error
+	URLHintContractAddress, err = xeth.Transact(addr, "", "100000000000", "1000000", "100000", ContractCodeURLhint)
+	if err != nil {
+		panic(err)
+	}
+	HashRegContractAddress, err = xeth.Transact(addr, "", "100000000000", "1000000", "100000", ContractCodeHashReg)
+	if err != nil {
+		panic(err)
+	}
+}
+
+type Resolver struct {
+	backend                Backend
+	urlHintContractAddress string
+	hashRegContractAddress string
+}
+
+type Backend interface {
+	StorageAt(string, string) string
+}
+
+func New(eth Backend, uhca, nrca string) *Resolver {
+	return &Resolver{eth, uhca, nrca}
+}
+
+func (self *Resolver) KeyToContentHash(khash common.Hash) (chash common.Hash, err error) {
+	// look up in hashReg
+	key := storageAddress(storageMapping(storageIdx2Addr(1), khash[:]))
+	hash := self.backend.StorageAt(self.hashRegContractAddress, key)
+
+	if hash == "0x0" || len(hash) < 3 {
+		err = fmt.Errorf("GetHashReg: content hash not found")
+		return
+	}
+
+	copy(chash[:], common.Hex2BytesFixed(hash[2:], 32))
+	return
+}
+
+func (self *Resolver) ContentHashToUrl(chash common.Hash) (uri string, err error) {
+	// look up in URL reg
+	var str string = " "
+	var idx uint32
+	for len(str) > 0 {
+		mapaddr := storageMapping(storageIdx2Addr(1), chash[:])
+		key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(idx)))
+		hex := self.backend.StorageAt(self.urlHintContractAddress, key)
+		str = string(common.Hex2Bytes(hex[2:]))
+		l := len(str)
+		for (l > 0) && (str[l-1] == 0) {
+			l--
+		}
+		str = str[:l]
+		uri = uri + str
+		idx++
+	}
+
+	if len(uri) == 0 {
+		err = fmt.Errorf("GetURLhint: URL hint not found")
+	}
+	return
+}
+
+func (self *Resolver) KeyToUrl(key common.Hash) (uri string, hash common.Hash, err error) {
+	// look up in urlHint
+	hash, err = self.KeyToContentHash(key)
+	if err != nil {
+		return
+	}
+	uri, err = self.ContentHashToUrl(hash)
+	return
+}
+
+func storageIdx2Addr(varidx uint32) []byte {
+	data := make([]byte, 32)
+	binary.BigEndian.PutUint32(data[28:32], varidx)
+	return data
+}
+
+func storageMapping(addr, key []byte) []byte {
+	data := make([]byte, 64)
+	copy(data[0:32], key[0:32])
+	copy(data[32:64], addr[0:32])
+	return crypto.Sha3(data)
+}
+
+func storageFixedArray(addr, idx []byte) []byte {
+	var carry byte
+	for i := 31; i >= 0; i-- {
+		var b byte = addr[i] + idx[i] + carry
+		if b < addr[i] {
+			carry = 1
+		} else {
+			carry = 0
+		}
+		addr[i] = b
+	}
+	return addr
+}
+
+func storageAddress(addr []byte) string {
+	return common.ToHex(addr)
+}

+ 88 - 0
common/resolver/resolver_test.go

@@ -0,0 +1,88 @@
+package resolver
+
+import (
+	"testing"
+
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/crypto"
+)
+
+type testBackend struct {
+	// contracts mock
+	contracts map[string](map[string]string)
+}
+
+var (
+	text     = "test"
+	codehash = common.StringToHash("1234")
+	hash     = common.BytesToHash(crypto.Sha3([]byte(text)))
+	url      = "bzz://bzzhash/my/path/contr.act"
+)
+
+func NewTestBackend() *testBackend {
+	self := &testBackend{}
+	self.contracts = make(map[string](map[string]string))
+
+	self.contracts[HashRegContractAddress] = make(map[string]string)
+	key := storageAddress(storageMapping(storageIdx2Addr(1), codehash[:]))
+	self.contracts[HashRegContractAddress][key] = hash.Hex()
+
+	self.contracts[URLHintContractAddress] = make(map[string]string)
+	mapaddr := storageMapping(storageIdx2Addr(1), hash[:])
+
+	key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(0)))
+	self.contracts[URLHintContractAddress][key] = common.ToHex([]byte(url))
+	key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(1)))
+	self.contracts[URLHintContractAddress][key] = "0x00"
+
+	return self
+}
+
+func (self *testBackend) StorageAt(ca, sa string) (res string) {
+	c := self.contracts[ca]
+	if c == nil {
+		return
+	}
+	res = c[sa]
+	return
+}
+
+func TestKeyToContentHash(t *testing.T) {
+	b := NewTestBackend()
+	res := New(b, URLHintContractAddress, HashRegContractAddress)
+
+	got, err := res.KeyToContentHash(codehash)
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	} else {
+		if got != hash {
+			t.Errorf("incorrect result, expected %x, got %x: ", hash.Hex(), got.Hex())
+		}
+	}
+}
+
+func TestContentHashToUrl(t *testing.T) {
+	b := NewTestBackend()
+	res := New(b, URLHintContractAddress, HashRegContractAddress)
+	got, err := res.ContentHashToUrl(hash)
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	} else {
+		if string(got) != url {
+			t.Errorf("incorrect result, expected %v, got %s: ", url, string(got))
+		}
+	}
+}
+
+func TestKeyToUrl(t *testing.T) {
+	b := NewTestBackend()
+	res := New(b, URLHintContractAddress, HashRegContractAddress)
+	got, _, err := res.KeyToUrl(codehash)
+	if err != nil {
+		t.Errorf("expected no error, got %v", err)
+	} else {
+		if string(got) != url {
+			t.Errorf("incorrect result, expected %v, got %s: ", url, string(got))
+		}
+	}
+}

+ 2 - 2
core/genesis.go

@@ -36,7 +36,7 @@ func GenesisBlock(db common.Database) *types.Block {
 		Balance string
 		Code    string
 	}
-	err := json.Unmarshal(genesisData, &accounts)
+	err := json.Unmarshal(GenesisData, &accounts)
 	if err != nil {
 		fmt.Println("enable to decode genesis json data:", err)
 		os.Exit(1)
@@ -57,7 +57,7 @@ func GenesisBlock(db common.Database) *types.Block {
 	return genesis
 }
 
-var genesisData = []byte(`{
+var GenesisData = []byte(`{
 	"0000000000000000000000000000000000000001": {"balance": "1"},
 	"0000000000000000000000000000000000000002": {"balance": "1"},
 	"0000000000000000000000000000000000000003": {"balance": "1"},

+ 3 - 0
eth/backend.go

@@ -50,6 +50,7 @@ type Config struct {
 	LogLevel int
 	LogJSON  string
 	VmDebug  bool
+	NatSpec  bool
 
 	MaxPeers int
 	Port     string
@@ -144,6 +145,7 @@ type Ethereum struct {
 	// logger logger.LogSystem
 
 	Mining        bool
+	NatSpec       bool
 	DataDir       string
 	etherbase     common.Address
 	clientVersion string
@@ -205,6 +207,7 @@ func New(config *Config) (*Ethereum, error) {
 		clientVersion:  config.Name, // TODO should separate from Name
 		ethVersionId:   config.ProtocolVersion,
 		netVersionId:   config.NetworkId,
+		NatSpec:        config.NatSpec,
 	}
 
 	eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux())

+ 7 - 1
rpc/api.go

@@ -2,7 +2,7 @@ package rpc
 
 import (
 	"encoding/json"
-	// "fmt"
+	"fmt"
 	"math/big"
 	"sync"
 
@@ -167,6 +167,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
 			return err
 		}
 
+		// call ConfirmTransaction first
+		tx, _ := json.Marshal(req)
+		if !api.xeth().ConfirmTransaction(string(tx)) {
+			return fmt.Errorf("Transaction not confirmed")
+		}
+
 		v, err := api.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data)
 		if err != nil {
 			return err

+ 3 - 7
xeth/frontend.go

@@ -1,9 +1,5 @@
 package xeth
 
-import (
-	"github.com/ethereum/go-ethereum/core/types"
-)
-
 // Frontend should be implemented by users of XEth. Its methods are
 // called whenever XEth makes a decision that requires user input.
 type Frontend interface {
@@ -21,12 +17,12 @@ type Frontend interface {
 	//
 	// ConfirmTransaction is not used for Call transactions
 	// because they cannot change any state.
-	ConfirmTransaction(tx *types.Transaction) bool
+	ConfirmTransaction(tx string) bool
 }
 
 // dummyFrontend is a non-interactive frontend that allows all
 // transactions but cannot not unlock any keys.
 type dummyFrontend struct{}
 
-func (dummyFrontend) UnlockAccount([]byte) bool                  { return false }
-func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true }
+func (dummyFrontend) UnlockAccount([]byte) bool      { return false }
+func (dummyFrontend) ConfirmTransaction(string) bool { return true }

+ 8 - 2
xeth/xeth.go

@@ -148,10 +148,10 @@ func (self *XEth) AtStateNum(num int64) *XEth {
 		}
 	}
 
-	return self.withState(st)
+	return self.WithState(st)
 }
 
-func (self *XEth) withState(statedb *state.StateDB) *XEth {
+func (self *XEth) WithState(statedb *state.StateDB) *XEth {
 	xeth := &XEth{
 		backend: self.backend,
 	}
@@ -608,6 +608,12 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
 	return common.ToHex(res), err
 }
 
+func (self *XEth) ConfirmTransaction(tx string) bool {
+
+	return self.frontend.ConfirmTransaction(tx)
+
+}
+
 func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) {
 	var (
 		from             = common.HexToAddress(fromStr)

Vissa filer visades inte eftersom för många filer har ändrats