This commit is contained in:
Viktor Trón 2016-01-23 05:29:58 +00:00
commit 9a1a25531e
30 changed files with 414 additions and 301 deletions

View file

@ -18,6 +18,7 @@ package main
import (
"bufio"
"encoding/json"
"fmt"
"math/big"
"os"
@ -30,9 +31,9 @@ import (
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/natspec"
"github.com/ethereum/go-ethereum/common/registrar"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/natspec"
"github.com/ethereum/go-ethereum/eth/registrar"
re "github.com/ethereum/go-ethereum/jsre"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
@ -368,15 +369,23 @@ func (self *jsre) AskPassword() (string, bool) {
return pass, true
}
func (self *jsre) ConfirmTransaction(tx string) bool {
func (self *jsre) ConfirmTransaction(txs string) bool {
// Retrieve the Ethereum instance from the node
var ethereum *eth.Ethereum
if err := self.stack.Service(&ethereum); err != nil {
return false
}
// If natspec is enabled, ask for permission
if ethereum.NatSpec {
notice := natspec.GetNotice(self.xeth, tx, ethereum.HTTPClient())
if ethereum.NatSpecEnabled {
var tx natspec.SendTxArgs
err := json.Unmarshal([]byte(txs), &tx)
if err != nil {
return false
}
notice, err := ethereum.NatSpec().GetNatSpec(&tx)
if err != nil {
return false
}
fmt.Println(notice)
answer, _ := self.Prompt("Confirm Transaction [y/n]")
return strings.HasPrefix(strings.Trim(answer, " "), "y")

View file

@ -604,6 +604,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
BootstrapNodes: MakeBootstrapNodes(ctx),
ListenAddr: MakeListenAddress(ctx),
NAT: MakeNAT(ctx),
DocRoot: ctx.GlobalString(DocRootFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
}
@ -620,8 +621,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
Etherbase: MakeEtherbase(accman, ctx),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
ExtraData: MakeMinerExtra(extra, ctx),
NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name),
DocRoot: ctx.GlobalString(DocRootFlag.Name),
NatSpecEnabled: ctx.GlobalBool(NatspecEnabledFlag.Name),
GasPrice: common.String2Big(ctx.GlobalString(GasPriceFlag.Name)),
GpoMinGasPrice: common.String2Big(ctx.GlobalString(GpoMinGasPriceFlag.Name)),
GpoMaxGasPrice: common.String2Big(ctx.GlobalString(GpoMaxGasPriceFlag.Name)),

View file

@ -23,9 +23,14 @@ import (
"encoding/hex"
"fmt"
"math/big"
"regexp"
"strings"
)
var (
hexRe = regexp.MustCompile("^(0x)?([a-fA-f0-9]{2})+$")
)
func ToHex(b []byte) string {
hex := Bytes2Hex(b)
// Prefer output of "0x0" instead of "0x"
@ -139,8 +144,14 @@ func HasHexPrefix(str string) bool {
}
func IsHex(str string) bool {
l := len(str)
return l >= 4 && l%2 == 0 && str[0:2] == "0x"
return hexRe.MatchString(str)
}
func NormaliseHex(str string) (string, bool) {
if HasHexPrefix(str) {
str = str[2:]
}
return str, IsHex(str)
}
func Bytes2Hex(d []byte) string {
@ -202,8 +213,8 @@ func ParseData(data ...interface{}) (ret []byte) {
switch t := item.(type) {
case string:
var str []byte
if IsHex(t) {
str = Hex2Bytes(t[2:])
if n, ok := NormaliseHex(t); ok {
str = Hex2Bytes(n)
} else {
str = []byte(t)
}

View file

@ -1,124 +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 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
}

View file

@ -32,12 +32,13 @@ import (
"github.com/ethereum/ethash"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/core"
"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/eth/compiler"
"github.com/ethereum/go-ethereum/eth/natspec"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
@ -882,19 +883,9 @@ func (s *PublicTransactionPoolAPI) sign(address common.Address, tx *types.Transa
return tx.WithSignature(signature)
}
type SendTxArgs struct {
From common.Address `json:"from"`
To common.Address `json:"to"`
Gas *rpc.HexNumber `json:"gas"`
GasPrice *rpc.HexNumber `json:"gasPrice"`
Value *rpc.HexNumber `json:"value"`
Data string `json:"data"`
Nonce *rpc.HexNumber `json:"nonce"`
}
// SendTransaction will create a transaction for the given transaction argument, sign it and submit it to the
// transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(args SendTxArgs) (common.Hash, error) {
func (s *PublicTransactionPoolAPI) SendTransaction(args *natspec.SendTxArgs) (common.Hash, error) {
if args.Gas == nil {
args.Gas = rpc.NewHexNumber(defaultGas)
}

View file

@ -30,12 +30,12 @@ import (
"github.com/ethereum/ethash"
"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/types"
"github.com/ethereum/go-ethereum/eth/compiler"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/natspec"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/logger"
@ -70,7 +70,6 @@ type Config struct {
DatabaseCache int
NatSpec bool
DocRoot string
AutoDAG bool
PowTest bool
ExtraData []byte
@ -80,6 +79,7 @@ type Config struct {
GasPrice *big.Int
MinerThreads int
SolcPath string
NatSpecEnabled bool
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
@ -108,6 +108,8 @@ type Ethereum struct {
protocolManager *ProtocolManager
SolcPath string
solc *compiler.Solidity
natSpec *natspec.NatSpec
NatSpecEnabled bool
GpoMinGasPrice *big.Int
GpoMaxGasPrice *big.Int
@ -116,14 +118,12 @@ type Ethereum struct {
GpobaseStepUp int
GpobaseCorrectionFactor int
httpclient *httpclient.HTTPClient
eventMux *event.TypeMux
http *node.HTTPClient
miner *miner.Miner
Mining bool
MinerThreads int
NatSpec bool
AutoDAG bool
PowTest bool
autodagquit chan bool
@ -196,9 +196,9 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
accountManager: config.AccountManager,
etherbase: config.Etherbase,
netVersionId: config.NetworkId,
NatSpec: config.NatSpec,
MinerThreads: config.MinerThreads,
SolcPath: config.SolcPath,
NatSpecEnabled: config.NatSpecEnabled,
AutoDAG: config.AutoDAG,
PowTest: config.PowTest,
GpoMinGasPrice: config.GpoMinGasPrice,
@ -207,9 +207,12 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
GpobaseStepDown: config.GpobaseStepDown,
GpobaseStepUp: config.GpobaseStepUp,
GpobaseCorrectionFactor: config.GpobaseCorrectionFactor,
httpclient: httpclient.New(config.DocRoot),
}
backend := NewPublicBlockChainAPI(eth.BlockChain(), eth.ChainDb(), eth.EventMux(), eth.AccountManager())
// eth.natSpec = natspec.New(backend, ctx.HTTP, registrar.New(backend))
eth.natSpec = natspec.New(backend, ctx.HTTP, nil)
if config.PowTest {
glog.V(logger.Info).Infof("ethash used in test mode")
eth.pow, err = ethash.NewForTesting()
@ -307,6 +310,10 @@ func (s *Ethereum) APIs() []rpc.API {
Namespace: "debug",
Version: "1.0",
Service: NewPrivateDebugAPI(s),
}, {
Namespace: "eth",
Version: "1.0",
Service: s.natSpec,
},
}
}
@ -327,6 +334,8 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
return
}
func (self *Ethereum) URLSchemes() []node.URLScheme { return nil }
// set in js console via admin interface or wrapper from cli flags
func (self *Ethereum) SetEtherbase(etherbase common.Address) {
self.etherbase = etherbase
@ -341,6 +350,7 @@ func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain }
func (s *Ethereum) TxPool() *core.TxPool { return s.txPool }
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
func (s *Ethereum) NatSpec() *natspec.NatSpec { return s.natSpec }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }
func (s *Ethereum) DappDb() ethdb.Database { return s.dappDb }
func (s *Ethereum) IsListening() bool { return true } // Always listening
@ -449,10 +459,10 @@ 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
// HTTP 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) HTTP() *node.HTTPClient {
return self.http
}
func (self *Ethereum) Solc() (*compiler.Solidity, error) {

View file

@ -23,58 +23,70 @@ import (
"strings"
"github.com/ethereum/go-ethereum/common"
"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"
"github.com/ethereum/go-ethereum/eth/registrar"
"github.com/ethereum/go-ethereum/node"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/robertkrimen/otto"
)
type abi2method map[[8]byte]*method
type NatSpec struct {
jsvm *otto.Otto
abiDocJson []byte
userDoc userDoc
tx, data string
backend Backend
http *node.HTTPClient
reg *registrar.Registrar
}
// main entry point for to get natspec notice for a transaction
// 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 *httpclient.HTTPClient) (notice string) {
ns, err := New(xeth, tx, http)
if err != nil {
if ns == nil {
return getFallbackNotice(fmt.Sprintf("no NatSpec info found for contract: %v", err), tx)
} else {
return getFallbackNotice(fmt.Sprintf("invalid NatSpec info: %v", err), tx)
}
}
notice, err = ns.Notice()
if err != nil {
return getFallbackNotice(fmt.Sprintf("NatSpec notice error: %v", err), tx)
}
return
type Backend interface {
GetCode(address common.Address, blockNr rpc.BlockNumber) (string, error)
}
func getFallbackNotice(comment, tx string) string {
return fmt.Sprintf("About to submit transaction (%s): %s", comment, tx)
}
type transaction struct {
To string `json:"to"`
Data string `json:"data"`
}
type jsonTx struct {
Params []transaction `json:"params"`
func New(backend Backend, http *node.HTTPClient, reg *registrar.Registrar) *NatSpec {
return &NatSpec{backend, http, reg}
}
type contractInfo struct {
jsvm *otto.Otto
abiDocJson []byte
userDoc userDoc
tx *SendTxArgs
}
type SendTxArgs struct {
From common.Address `json:"from"`
To common.Address `json:"to"`
Gas *rpc.HexNumber `json:"gas"`
GasPrice *rpc.HexNumber `json:"gasPrice"`
Value *rpc.HexNumber `json:"value"`
Data string `json:"data"`
Nonce *rpc.HexNumber `json:"nonce"`
}
func (self *NatSpec) GetNatSpec(tx *SendTxArgs) (string, error) {
to := tx.To
// in principle this could be cached, but practically swarm takes care of that
info, err := self.GetContractInfo(to)
if err != nil {
if info == nil {
return "", fmt.Errorf("no contract info found for %v: %v", to, err)
} else {
return "", fmt.Errorf("invalid contract info for %v: %v", to, err)
}
}
ns, err := newContractInfo(info, tx)
if err != nil {
return "", fmt.Errorf("invalid contract info for %v: %v", to, err)
}
notice, err := ns.notice()
if err != nil {
return "", fmt.Errorf("NatSpec notice error for contract %v: %v", to, err)
}
return notice, nil
}
type ContractInfo struct {
Source string `json:"source"`
Language string `json:"language"`
Version string `json:"compilerVersion"`
@ -83,81 +95,56 @@ type contractInfo struct {
DeveloperDoc json.RawMessage `json:"developerDoc"`
}
func New(xeth *xeth.XEth, jsontx string, http *httpclient.HTTPClient) (self *NatSpec, err error) {
// extract contract address from tx
var tx jsonTx
err = json.Unmarshal([]byte(jsontx), &tx)
if err != nil {
return
}
t := tx.Params[0]
contractAddress := t.To
content, err := FetchDocsForContract(contractAddress, xeth, http)
if err != nil {
return
}
self, err = NewWithDocs(content, jsontx, t.Data)
return
}
// also called by admin.contractInfo.get
func FetchDocsForContract(contractAddress string, xeth *xeth.XEth, client *httpclient.HTTPClient) (content []byte, err error) {
func (self *NatSpec) GetContractInfo(to common.Address) (*ContractInfo, error) {
// retrieve contract hash from state
codehex := xeth.CodeAt(contractAddress)
codeb := xeth.CodeAtBytes(contractAddress)
if codehex == "0x" {
err = fmt.Errorf("contract (%v) not found", contractAddress)
return
codehex, err := self.backend.GetCode(to, -1)
if err != nil || len(codehex) <= 3 {
return nil, fmt.Errorf("contract (%v) not found", to)
}
codehash := common.BytesToHash(crypto.Sha3(codeb))
// set up nameresolver with natspecreg + urlhint contract addresses
reg := registrar.New(xeth)
codehash := common.BytesToHash(crypto.Sha3(common.FromHex(codehex)))
// resolve host via HashReg/UrlHint Resolver
hash, err := reg.HashToHash(codehash)
hash, err := self.reg.HashToHash(codehash)
if err != nil {
return
return nil, fmt.Errorf("no content hash registered for contract %v: %v", to, err)
}
if client.HasScheme("bzz") {
content, err = client.Get("bzz://"+hash.Hex()[2:], "")
if err == nil { // non-fatal
return
var data []byte
if self.http.HasScheme("bzz") {
data, err = self.http.GetBody("bzz://" + hash.Hex()[2:])
if err != nil { // non-fatal
data = nil
}
err = nil
//falling back to urlhint
}
uri, err := reg.HashToUrl(hash)
if err != nil {
return
//falling back to urlhint
if data == nil {
uri, err := self.reg.HashToUrl(hash)
if err != nil {
return nil, err
}
// get content via http client and authenticate content using hash
data, err = self.http.GetAuthBody(uri, hash)
if err != nil {
return nil, err
}
}
// get content via http client and authenticate content using hash
content, err = client.GetAuthContent(uri, hash)
var info ContractInfo
err = json.Unmarshal(data, &info)
if err != nil {
return
return nil, err
}
return
return &info, nil
}
func NewWithDocs(infoDoc []byte, tx string, data string) (self *NatSpec, err error) {
func newContractInfo(info *ContractInfo, tx *SendTxArgs) (self *contractInfo, err error) {
var contract contractInfo
err = json.Unmarshal(infoDoc, &contract)
if err != nil {
return
}
self = &NatSpec{
self = &contractInfo{
jsvm: otto.New(),
abiDocJson: []byte(contract.AbiDefinition),
userDoc: contract.UserDoc,
abiDocJson: []byte(info.AbiDefinition),
userDoc: info.UserDoc,
tx: tx,
data: data,
}
// load and require natspec js (but it is meant to be protected environment)
@ -192,7 +179,7 @@ type userDoc struct {
Methods map[string]*method `json:methods`
}
func (self *NatSpec) makeAbi2method(abiKey [8]byte) (meth *method) {
func (self *contractInfo) 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))))
@ -207,26 +194,30 @@ func (self *NatSpec) makeAbi2method(abiKey [8]byte) (meth *method) {
return
}
func (self *NatSpec) Notice() (notice string, err error) {
func (self *contractInfo) notice() (notice string, err error) {
var abiKey [8]byte
if len(self.data) < 10 {
if len(self.tx.Data) < 10 {
err = fmt.Errorf("Invalid transaction data")
return
}
copy(abiKey[:], self.data[2:10])
copy(abiKey[:], self.tx.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)
notice, err = self.noticeForMethod(meth.name, meth.Notice)
return
}
func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice string, err error) {
func (self *contractInfo) noticeForMethod(name, expression string) (notice string, err error) {
tx, err := json.Marshal(self.tx)
if err != nil {
return "", nil
}
if _, err = self.jsvm.Run("var transaction = " + tx + ";"); err != nil {
if _, err = self.jsvm.Run("var transaction = " + string(tx) + ";"); err != nil {
return "", fmt.Errorf("natspec.js error setting transaction: %v", err)
}
@ -247,12 +238,13 @@ func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice
if err != nil {
return "", fmt.Errorf("natspec.js error evaluating expression: %v", err)
}
evalError := "Natspec evaluation failed, wrong input params"
if value.String() == evalError {
return "", fmt.Errorf("natspec.js error evaluating expression: wrong input params in expression '%s'", expression)
}
if len(value.String()) == 0 {
return "", fmt.Errorf("natspec.js error evaluating expression")
return "", fmt.Errorf("natspec.js error evaluating expression: empty notice")
}
return value.String(), nil

View file

@ -49,6 +49,10 @@ type Config struct {
// in memory.
DataDir string
// Document Root, durectory path for serving files from local file system
// used as root for Node#HTTP.Get("file://") requestss
DocRoot string
// This field should be a valid secp256k1 private key that will be used for both
// remote peer identification as well as network traffic encryption. If no key
// is configured, the preset one is loaded from the data dir, generating it if

139
node/http.go Normal file
View file

@ -0,0 +1,139 @@
package node
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func newHTTP(docRoot string) *HTTPClient {
t := &http.Transport{}
if len(docRoot) > 0 {
t.RegisterProtocol("file", http.NewFileTransport(http.Dir(docRoot)))
}
return &HTTPClient{
Client: &http.Client{Transport: t},
transport: t,
schemes: []string{"file"},
}
}
func (self *HTTPClient) registerSchemes(schemes []URLScheme) {
for _, scheme := range schemes {
self.schemes = append(self.schemes, scheme.Name)
self.transport.RegisterProtocol(scheme.Name, scheme.RoundTripper)
}
}
type HTTPClient struct {
*http.Client
transport *http.Transport
schemes []string
}
// 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.
type URLScheme struct {
Name string
RoundTripper http.RoundTripper
}
func (self *HTTPClient) HasScheme(scheme string) bool {
for _, s := range self.schemes {
if s == scheme {
return true
}
}
return false
}
// GetBody(uri) downloads the document at uri, if path is non-empty it
// GetBody(uri) fetches the uri and returns the body
func (self *HTTPClient) GetBody(uri string) ([]byte, error) {
// retrieve body
resp, err := self.Get(uri)
if err != nil {
return nil, err
}
defer func() {
if resp != nil {
resp.Body.Close()
}
}()
var body []byte
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode/100 != 2 {
return body, fmt.Errorf("HTTP error: %s", resp.Status)
}
return body, nil
}
// Download(uri, path) fetches the uri and writes the body into the file
func (self *HTTPClient) Download(uri, path string) error {
resp, err := self.Get(uri)
if err != nil {
return err
}
defer func() {
if resp != nil {
resp.Body.Close()
}
}()
var abspath string
abspath, err = filepath.Abs(path)
if err != nil {
return err
}
w, err := os.Create(abspath)
if err != nil {
return err
}
defer w.Close()
_, err = io.Copy(w, resp.Body)
if err != nil {
return err
}
return nil
}
func (self *HTTPClient) GetAuthBody(uri string, hash common.Hash) ([]byte, error) {
// retrieve body
body, err := self.GetBody(uri)
if err != nil {
return nil, err
}
// check hash to authenticate body
chash := crypto.Sha3Hash(body)
if chash != hash {
return nil, fmt.Errorf("body hash mismatch %x != %x (exp)", hash[:], chash[:])
}
return body, nil
}
// type HTTPClientAPI interface {
// Get(uri string) (*http.Response, err)
// Download(uri, path string) error
// GetBody(uri) ([]byte, error)
// HasScheme(scheme string) bool
// GetAuthBody(uri string, hash common.Hash) ([]byte, error)
// }

View file

@ -14,9 +14,10 @@
// 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
package node
import (
"errors"
"io/ioutil"
"net/http"
"os"
@ -27,20 +28,20 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)
func TestGetAuthContent(t *testing.T) {
func TestGetAuthBody(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)
client := newHTTP(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)
content, err := client.GetAuthBody("file:///test.content", hash)
if err != nil {
t.Errorf("no error expected, got %v", err)
}
@ -49,8 +50,8 @@ func TestGetAuthContent(t *testing.T) {
}
hash = common.Hash{}
content, err = client.GetAuthContent("file:///test.content", hash)
expected := "content hash mismatch 0000000000000000000000000000000000000000000000000000000000000000 != 9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658 (exp)"
content, err = client.GetAuthBody("file:///test.content", hash)
expected := "body hash mismatch 0000000000000000000000000000000000000000000000000000000000000000 != 9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658 (exp)"
if err == nil {
t.Errorf("expected error, got nothing")
} else {
@ -61,17 +62,26 @@ func TestGetAuthContent(t *testing.T) {
}
type rt struct{}
type rterr error
type rt struct{ error }
func (rt) RoundTrip(req *http.Request) (resp *http.Response, err error) { return }
// roundtripper with an error. this can prove the roundtrip for testing
// without having the need to write a Response
func (e rt) RoundTrip(req *http.Request) (resp *http.Response, err error) {
return &http.Response{}, e.error
}
func TestRegisterScheme(t *testing.T) {
client := New("/tmp/")
func TestHasScheme(t *testing.T) {
client := newHTTP("")
if client.HasScheme("scheme") {
t.Errorf("expected scheme not to be registered")
}
client.RegisterScheme("scheme", rt{})
client.registerSchemes([]URLScheme{{"scheme", &rt{rterr(errors.New("rt"))}}})
if !client.HasScheme("scheme") {
t.Errorf("expected scheme to be registered")
}
body, err := client.GetBody("scheme://url.com")
if _, ok := err.(rterr); !ok {
t.Fatalf("failed to use registered scheme. Got error %v and body %v", body, err)
}
}

View file

@ -44,6 +44,7 @@ var (
type Node struct {
datadir string // Path to the currently used data directory
eventmux *event.TypeMux // Event multiplexer used between the services of a stack
http *HTTPClient // HTTP Client that services can register their URL schemes
serverConfig *p2p.Server // Configuration of the underlying P2P networking layer
server *p2p.Server // Currently running P2P networking layer
@ -68,6 +69,7 @@ func New(conf *Config) (*Node, error) {
if conf.DataDir != "" {
nodeDbPath = filepath.Join(conf.DataDir, datadirNodeDatabase)
}
httpClient := newHTTP(conf.DocRoot)
return &Node{
datadir: conf.DataDir,
serverConfig: &p2p.Server{
@ -87,6 +89,7 @@ func New(conf *Config) (*Node, error) {
},
serviceFuncs: []ServiceConstructor{},
eventmux: new(event.TypeMux),
http: httpClient,
}, nil
}
@ -123,6 +126,7 @@ func (n *Node) Start() error {
datadir: n.datadir,
services: make(map[reflect.Type]Service),
EventMux: n.eventmux,
HTTP: n.http,
}
for kind, s := range services { // copy needed for threaded access
ctx.services[kind] = s
@ -132,6 +136,7 @@ func (n *Node) Start() error {
if err != nil {
return err
}
n.http.registerSchemes(service.URLSchemes())
kind := reflect.TypeOf(service)
if _, exists := services[kind]; exists {
return &DuplicateServiceError{Kind: kind}
@ -163,6 +168,7 @@ func (n *Node) Start() error {
// Mark the service started for potential cleanup
started = append(started, kind)
}
// Finish initializing the startup
n.services = services
n.server = running
@ -290,6 +296,10 @@ func (n *Node) APIs() []rpc.API {
Version: "1.0",
Service: NewPublicDebugAPI(n),
Public: true,
}, {
Namespace: "http",
Version: "1.0",
Service: n.http,
},
}
// Inject all the APIs owned by various services

View file

@ -31,14 +31,17 @@ import (
//
// The following methods are needed to implement a node.Service:
// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on
// - APIs() []rpc.API - APIs exposed to the rpc
// - URLSchemes() []URLScheme - URL schemes handled by the service registered on Node#HTTP
// - Start() error - method invoked when the node is ready to start the service
// - Stop() error - method invoked when the node terminates the service
type SampleService struct{}
func (s *SampleService) Protocols() []p2p.Protocol { return nil }
func (s *SampleService) APIs() []rpc.API { return nil }
func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil }
func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil }
func (s *SampleService) Protocols() []p2p.Protocol { return nil }
func (s *SampleService) APIs() []rpc.API { return nil }
func (s *SampleService) URLSchemes() []node.URLScheme { return nil }
func (s *SampleService) Start(*p2p.Server) error { fmt.Println("Service starting..."); return nil }
func (s *SampleService) Stop() error { fmt.Println("Service stopping..."); return nil }
func ExampleUsage() {
// Create a network node to run protocols with the default values. The below list

View file

@ -18,6 +18,7 @@ package node
import (
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
@ -131,6 +132,36 @@ func TestServiceRegistry(t *testing.T) {
}
}
// Tests whether services can be registered and duplicates caught.
func TestServiceURLSchemes(t *testing.T) {
stack, err := New(testNodeConfig)
if err != nil {
t.Fatalf("failed to create protocol stack: %v", err)
}
var h *HTTPClient
newSchemeUserService := func(ctx *ServiceContext) (Service, error) {
h = ctx.HTTP
return new(SchemeUserService), nil
}
// Register a batch of unique services and ensure they start successfully
services := []ServiceConstructor{NewSchemeService, newSchemeUserService}
for i, constructor := range services {
if err := stack.Register(constructor); err != nil {
t.Fatalf("service #%d: registration failed: %v", i, err)
}
}
if err := stack.Start(); err != nil {
t.Fatalf("failed to start original service stack: %v", err)
}
defer stack.Stop()
body, err := h.GetBody("rt://url.com")
if _, ok := err.(rterr); !ok {
t.Fatalf("failed to use registered scheme. Got error %v and body %v", body, err)
}
}
// Tests that registered services get started and stopped correctly.
func TestServiceLifeCycle(t *testing.T) {
stack, err := New(testNodeConfig)

View file

@ -33,6 +33,7 @@ type ServiceContext struct {
datadir string // Data directory for protocol persistence
services map[reflect.Type]Service // Index of the already constructed services
EventMux *event.TypeMux // Event multiplexer used for decoupled notifications
HTTP *HTTPClient // HTTP Client services register their URL schemes with
}
// OpenDatabase opens an existing database with the given name (or creates one
@ -74,6 +75,9 @@ type Service interface {
// APIs retrieves the list of RPC descriptors the service provides
APIs() []rpc.API
// Url Schemes to register on the node http client
URLSchemes() []URLScheme
// Start is called after all services have been constructed and the networking
// layer was also initialized to spawn any goroutines required by the service.
Start(server *p2p.Server) error

View file

@ -20,6 +20,7 @@
package node
import (
"errors"
"reflect"
"github.com/ethereum/go-ethereum/p2p"
@ -31,6 +32,7 @@ type NoopService struct{}
func (s *NoopService) Protocols() []p2p.Protocol { return nil }
func (s *NoopService) APIs() []rpc.API { return nil }
func (s *NoopService) URLSchemes() []URLScheme { return nil }
func (s *NoopService) Start(*p2p.Server) error { return nil }
func (s *NoopService) Stop() error { return nil }
@ -48,6 +50,26 @@ func NewNoopServiceB(*ServiceContext) (Service, error) { return new(NoopServiceB
func NewNoopServiceC(*ServiceContext) (Service, error) { return new(NoopServiceC), nil }
func NewNoopServiceD(*ServiceContext) (Service, error) { return new(NoopServiceD), nil }
// Scheme Service is a service that registers a url scheme on the node
type SchemeService struct{ NoopService }
func NewSchemeService(*ServiceContext) (Service, error) { return new(SchemeService), nil }
// func (s *NoopService) Protocols() []p2p.Protocol { return nil }
// func (s *NoopService) APIs() []rpc.API { return nil }
// func (s *NoopService) URLSchemes() []URLScheme { return nil }
// func (s *NoopService) Start(*p2p.Server) error { return nil }
// func (s *NoopService) Stop() error { return nil }
func (*SchemeService) URLSchemes() []URLScheme {
return []URLScheme{{"rt", &rt{rterr(errors.New("rt"))}}}
}
// Scheme User Service is a service that expects a http client on the service context
type SchemeUserService struct {
NoopService
}
// InstrumentedService is an implementation of Service for which all interface
// methods can be instrumented both return value as well as event hook wise.
type InstrumentedService struct {
@ -73,6 +95,10 @@ func (s *InstrumentedService) APIs() []rpc.API {
return nil
}
func (s *InstrumentedService) URLSchemes() []URLScheme {
return nil
}
func (s *InstrumentedService) Start(server *p2p.Server) error {
if s.startHook != nil {
s.startHook(server)

View file

@ -24,13 +24,13 @@ import (
"time"
"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/registrar"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/compiler"
// "github.com/ethereum/go-ethereum/eth/natspec"
"github.com/ethereum/go-ethereum/eth/registrar"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p/discover"
@ -426,12 +426,12 @@ func (self *adminApi) RegisterUrl(req *shared.Request) (interface{}, error) {
}
func (self *adminApi) StartNatSpec(req *shared.Request) (interface{}, error) {
self.ethereum.NatSpec = true
self.ethereum.NatSpecEnabled = true
return true, nil
}
func (self *adminApi) StopNatSpec(req *shared.Request) (interface{}, error) {
self.ethereum.NatSpec = false
self.ethereum.NatSpecEnabled = false
return true, nil
}
@ -441,13 +441,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.ethereum.HTTPClient())
if err != nil {
return nil, err
}
var info interface{}
err = self.coder.Decode(infoDoc, &info)
info, err := self.ethereum.NatSpec().GetContractInfo(common.HexToAddress(args.Contract))
if err != nil {
return nil, err
}
@ -461,12 +455,12 @@ func (self *adminApi) HttpGet(req *shared.Request) (interface{}, error) {
return nil, shared.NewDecodeParamError(err.Error())
}
resp, err := self.ethereum.HTTPClient().Get(args.Uri, args.Path)
body, err := self.ethereum.HTTP().GetBody(args.Uri)
if err != nil {
return nil, err
}
return string(resp), nil
return string(body), nil
}
func (self *adminApi) EnableUserAgent(req *shared.Request) (interface{}, error) {

View file

@ -19,7 +19,7 @@ package api
import (
"encoding/json"
"github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/eth/compiler"
"github.com/ethereum/go-ethereum/rpc/shared"
)

View file

@ -24,8 +24,8 @@ 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/eth/natspec"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc/codec"
"github.com/ethereum/go-ethereum/rpc/shared"
@ -372,10 +372,11 @@ func (self *ethApi) GetNatSpec(req *shared.Request) (interface{}, error) {
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
tx := &natspec.SendTxArgs{
To: common.HexToAddress(args.To),
Data: args.Data,
}
return self.ethereum.NatSpec().GetNatSpec(tx)
}
func (self *ethApi) EstimateGas(req *shared.Request) (interface{}, error) {

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/event/filter"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
@ -112,6 +113,8 @@ func (s *Whisper) APIs() []rpc.API {
}
}
func (self *Whisper) URLSchemes() []node.URLScheme { return nil }
// Protocols returns the whisper sub-protocols ran by this particular client.
func (self *Whisper) Protocols() []p2p.Protocol {
return []p2p.Protocol{self.protocol}

View file

@ -29,13 +29,13 @@ 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/core"
"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/eth"
"github.com/ethereum/go-ethereum/eth/compiler"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
@ -865,7 +865,8 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st
return common.ToHex(res), gas.String(), err
}
func (self *XEth) ConfirmTransaction(tx string) bool {
func (self *XEth) ConfirmTransaction(to, data string) bool {
tx := fmt.Sprintf(`{"params":[{"to":"%s","data": "%s"}]}`, to, data)
return self.frontend.ConfirmTransaction(tx)
}
@ -962,9 +963,7 @@ func (self *XEth) SignTransaction(fromStr, toStr, nonceStr, valueStr, gasStr, ga
func (self *XEth) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) {
// this minimalistic recoding is enough (works for natspec.js)
var jsontx = fmt.Sprintf(`{"params":[{"to":"%s","data": "%s"}]}`, toStr, codeStr)
if !self.ConfirmTransaction(jsontx) {
if !self.ConfirmTransaction(toStr, codeStr) {
err := fmt.Errorf("Transaction not confirmed")
return "", err
}