diff --git a/cmd/geth/js_bzz_test.go b/cmd/geth/js_bzz_test.go
deleted file mode 100644
index 2d8b70efb9..0000000000
--- a/cmd/geth/js_bzz_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package main
-
-import (
- "io/ioutil"
- "os"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/swarm"
- "github.com/ethereum/go-ethereum/swarm/api"
-)
-
-var port = 8500
-
-func bzzREPL(t *testing.T, configf func(*api.Config)) (string, string, *testjethre, *node.Node) {
- prvKey, err := crypto.GenerateKey()
- if err != nil {
- t.Fatal("unable to generate key")
- }
- bzztmp, err := ioutil.TempDir("", "bzz-js-test")
- config, err := api.NewConfig(bzztmp, common.Address{}, prvKey)
- if err != nil {
- t.Fatal("unable to configure swarm")
- }
- if configf != nil {
- configf(config)
- }
- tmp, repl, stack := testREPL(t, func(n *node.Node) {
- if err := n.Register(func(ctx *node.ServiceContext) (node.Service, error) {
- return swarm.NewSwarm(ctx, config, false)
- }); err != nil {
- t.Fatalf("Failed to register the Swarm service: %v", err)
- }
- })
- return bzztmp, tmp, repl, stack
-}
-
-func withREPL(t *testing.T, cf func(*api.Config), f func(repl *testjethre)) {
- bzztmp, tmp, repl, stack := bzzREPL(t, cf)
- defer stack.Stop()
- defer os.RemoveAll(tmp)
- defer os.RemoveAll(bzztmp)
- f(repl)
-}
-
-func TestBzzPutGet(t *testing.T) {
- withREPL(t,
- func(c *api.Config) {
- c.Port = ""
- }, func(repl *testjethre) {
- if checkEvalJSON(t, repl, `hash = bzz.put("console.log(\"hello from console\")", "application/javascript")`, `"97f1b7c7ea12468fd37c262383b9aa862d0cfbc4fc7218652374679fc5cf40cd"`) != nil {
- return
- }
- want := `{"content":"console.log(\"hello from console\")","contentType":"application/javascript","size":"33","status":"0"}`
- if checkEvalJSON(t, repl, `bzz.get(hash)`, want) != nil {
- return
- }
- })
-}
-
-// the server can be initialized only once per test session !
-// until we implement a stoppable http server
-// further http tests will need to make sure the correct server is running
-func TestHTTP(t *testing.T) {
- withREPL(t, nil, func(repl *testjethre) {
- if checkEvalJSON(t, repl, `hash = bzz.put("f42 = function() { return 42 }", "application/javascript")`, `"e6847876f00102441f850b2d438a06d10e3bf24e6a0a76d47b073a86c3c2f9ac"`) != nil {
- return
- }
- if checkEvalJSON(t, repl, `admin.httpGet("bzz://"+hash)`, `"f42 = function() { return 42 }"`) != nil {
- return
- }
-
- // if checkEvalJSON(t, repl, `http.loadScript("bzz://"+hash)`, `true`) != nil {
- // return
- // }
-
- // if checkEvalJSON(t, repl, `f42()`, `42`) != nil {
- // return
- // }
- })
-}
diff --git a/common/chequebook/api.go b/common/chequebook/api.go
new file mode 100644
index 0000000000..5507682530
--- /dev/null
+++ b/common/chequebook/api.go
@@ -0,0 +1,29 @@
+package chequebook
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+)
+
+const Version = "1.0"
+
+type Api struct {
+ ch *Chequebook
+}
+
+func NewApi(ch *Chequebook) *Api {
+ return &Api{ch}
+}
+
+func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) {
+ return self.ch.Issue(beneficiary, amount)
+}
+
+func (self *Api) Cash(cheque *Cheque) (txhash string, err error) {
+ return self.ch.Cash(cheque)
+}
+
+func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
+ return self.ch.Deposit(amount)
+}
diff --git a/rpc/api/bzz.go b/rpc/api/bzz.go
deleted file mode 100644
index c258879857..0000000000
--- a/rpc/api/bzz.go
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum 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.
-//
-// go-ethereum 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 go-ethereum. If not, see .
-
-package api
-
-import (
- "encoding/json"
- "fmt"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/node"
- "github.com/ethereum/go-ethereum/rpc/codec"
- "github.com/ethereum/go-ethereum/rpc/shared"
- "github.com/ethereum/go-ethereum/swarm"
-)
-
-const (
- BzzApiVersion = "1.0"
-)
-
-// eth api provider
-// See https://github.com/ethereum/wiki/wiki/JSON-RPC
-type bzzApi struct {
- swarm *swarm.Swarm
- methods map[string]bzzhandler
- codec codec.ApiCoder
-}
-
-// eth callback handler
-type bzzhandler func(*bzzApi, *shared.Request) (interface{}, error)
-
-var (
- bzzMapping = map[string]bzzhandler{
- "bzz_info": (*bzzApi).Info,
- "bzz_issue": (*bzzApi).Issue,
- "bzz_cash": (*bzzApi).Cash,
- "bzz_deposit": (*bzzApi).Deposit,
- "bzz_register": (*bzzApi).Register,
- "bzz_resolve": (*bzzApi).Resolve,
- "bzz_download": (*bzzApi).Download,
- "bzz_upload": (*bzzApi).Upload,
- "bzz_get": (*bzzApi).Get,
- "bzz_put": (*bzzApi).Put,
- "bzz_modify": (*bzzApi).Modify,
- }
-)
-
-func newSwarmOfflineError(method string) error {
- return shared.NewNotAvailableError(method, "swarm offline")
-}
-
-// create new bzzApi instance
-func NewBzzApi(stack *node.Node, codec codec.Codec) *bzzApi {
- var swarm *swarm.Swarm
- stack.Service(&swarm)
- return &bzzApi{swarm, bzzMapping, codec.New(nil)}
-}
-
-// collection with supported methods
-func (self *bzzApi) Methods() []string {
- methods := make([]string, len(self.methods))
- i := 0
- for k := range self.methods {
- methods[i] = k
- i++
- }
- return methods
-}
-
-// Execute given request
-func (self *bzzApi) Execute(req *shared.Request) (interface{}, error) {
- if callback, ok := self.methods[req.Method]; ok {
- return callback(self, req)
- }
-
- return nil, shared.NewNotImplementedError(req.Method)
-}
-
-func (self *bzzApi) Name() string {
- return shared.BzzApiName
-}
-
-func (self *bzzApi) ApiVersion() string {
- return BzzApiVersion
-}
-
-func (self *bzzApi) Info(req *shared.Request) (interface{}, error) {
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
- return s.Api().Info(), nil
-}
-
-func (self *bzzApi) Issue(req *shared.Request) (interface{}, error) {
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzIssueArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- cheque, err := s.Api().Issue(common.HexToAddress(args.Beneficiary), args.Amount)
- if err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- out, err := json.MarshalIndent(cheque, " ", "")
- if err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- return string(out), nil
-}
-
-func (self *bzzApi) Cash(req *shared.Request) (interface{}, error) {
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzCashArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- return s.Api().Cash(args.Cheque)
-
-}
-
-func (self *bzzApi) Deposit(req *shared.Request) (interface{}, error) {
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzDepositArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- return s.Api().Deposit(args.Amount)
-}
-
-func (self *bzzApi) Register(req *shared.Request) (interface{}, error) {
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzRegisterArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- err := s.Api().Register(common.HexToAddress(args.Address), args.Domain, common.HexToHash(args.ContentHash))
- return err == nil, err
-}
-
-func (self *bzzApi) Resolve(req *shared.Request) (interface{}, error) {
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzResolveArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- key, err := s.Api().Resolve(args.Domain)
- return key.Hex(), err
-}
-
-func (self *bzzApi) Download(req *shared.Request) (interface{}, error) {
-
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzDownloadArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- err := s.Api().Download(args.BzzPath, args.LocalPath)
- return err == nil, err
-}
-
-func (self *bzzApi) Upload(req *shared.Request) (interface{}, error) {
-
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzUploadArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- return s.Api().Upload(args.LocalPath, args.Index)
-}
-
-func (self *bzzApi) Get(req *shared.Request) (interface{}, error) {
-
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzGetArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- var content []byte
- var mimeType string
- var status, size int
- var err error
- content, mimeType, status, size, err = s.Api().Get(args.Path)
-
- obj := map[string]string{
- "content": string(content),
- "contentType": mimeType,
- "status": fmt.Sprintf("%v", status),
- "size": fmt.Sprintf("%v", size),
- }
-
- return obj, err
-}
-
-func (self *bzzApi) Put(req *shared.Request) (interface{}, error) {
-
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzPutArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- return s.Api().Put(args.Content, args.ContenType)
-}
-
-func (self *bzzApi) Modify(req *shared.Request) (interface{}, error) {
-
- s := self.swarm
- if s == nil {
- return nil, newSwarmOfflineError(req.Method)
- }
-
- args := new(BzzModifyArgs)
- if err := self.codec.Decode(req.Params, &args); err != nil {
- return nil, shared.NewDecodeParamError(err.Error())
- }
-
- return s.Api().Modify(args.RootHash, args.Path, args.ContentHash, args.ContentType)
-}
diff --git a/rpc/api/bzz_args.go b/rpc/api/bzz_args.go
deleted file mode 100644
index 2aee255e15..0000000000
--- a/rpc/api/bzz_args.go
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum 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.
-//
-// go-ethereum 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 go-ethereum. If not, see .
-
-package api
-
-import (
- "encoding/json"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common/chequebook"
- "github.com/ethereum/go-ethereum/rpc/shared"
-)
-
-type BzzDepositArgs struct {
- Amount *big.Int
-}
-
-func (args *BzzDepositArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 1 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- amount, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Amount", "not a string")
- }
- args.Amount, ok = new(big.Int).SetString(amount, 10)
- if !ok {
- return shared.NewInvalidTypeError("Amount", "not a number")
- }
-
- return nil
-}
-
-type BzzCashArgs struct {
- Cheque *chequebook.Cheque
-}
-
-func (args *BzzCashArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 1 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- chequestr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Cheque", "not a string")
- }
- var cheque chequebook.Cheque
- err = json.Unmarshal([]byte(chequestr), &cheque)
- if err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
- args.Cheque = &cheque
-
- return nil
-}
-
-type BzzIssueArgs struct {
- Beneficiary string
- Amount *big.Int
-}
-
-func (args *BzzIssueArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 2 {
- return shared.NewInsufficientParamsError(len(obj), 2)
- }
-
- beneficiary, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Amount", "not a string")
- }
- args.Beneficiary = beneficiary
-
- amount, ok := obj[1].(string)
- if !ok {
- return shared.NewInvalidTypeError("Amount", "not a string")
- }
- args.Amount, ok = new(big.Int).SetString(amount, 10)
- if !ok {
- return shared.NewInvalidTypeError("Amount", "not a number")
- }
-
- return nil
-}
-
-type BzzRegisterArgs struct {
- Address, ContentHash, Domain string
-}
-
-func (args *BzzRegisterArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 3 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Address", "not a string")
- }
- args.Address = addstr
-
- addstr, ok = obj[1].(string)
- if !ok {
- return shared.NewInvalidTypeError("Domain", "not a string")
- }
- args.Domain = addstr
-
- addstr, ok = obj[2].(string)
- if !ok {
- return shared.NewInvalidTypeError("ContentHash", "not a string")
- }
- args.ContentHash = addstr
-
- return nil
-}
-
-type BzzResolveArgs struct {
- Domain string
-}
-
-func (args *BzzResolveArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 1 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Domain", "not a string")
- }
- args.Domain = addstr
-
- return nil
-}
-
-type BzzDownloadArgs struct {
- BzzPath, LocalPath string
-}
-
-func (args *BzzDownloadArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 2 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("BzzPath", "not a string")
- }
- args.BzzPath = addstr
-
- addstr, ok = obj[1].(string)
- if !ok {
- return shared.NewInvalidTypeError("LocalPath", "not a string")
- }
- args.LocalPath = addstr
-
- return nil
-}
-
-type BzzUploadArgs struct {
- LocalPath, Index string
-}
-
-func (args *BzzUploadArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 1 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("LocalPath", "not a string")
- }
- args.LocalPath = addstr
-
- if len(obj) > 1 {
- addstr, ok := obj[1].(string)
- if ok {
- args.Index = addstr
- }
- }
-
- return nil
-}
-
-type BzzGetArgs struct {
- Path string
-}
-
-func (args *BzzGetArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 1 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Path", "not a string")
- }
- args.Path = addstr
-
- return nil
-}
-
-type BzzPutArgs struct {
- Content, ContenType string
-}
-
-func (args *BzzPutArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 1 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("Content", "not a string")
- }
- args.Content = addstr
-
- addstr, ok = obj[1].(string)
- if !ok {
- return shared.NewInvalidTypeError("ContenType", "not a string")
- }
- args.ContenType = addstr
-
- return nil
-}
-
-type BzzModifyArgs struct {
- RootHash, Path, ContentHash, ContentType string
-}
-
-func (args *BzzModifyArgs) UnmarshalJSON(b []byte) (err error) {
- var obj []interface{}
- if err := json.Unmarshal(b, &obj); err != nil {
- return shared.NewDecodeParamError(err.Error())
- }
-
- if len(obj) < 2 {
- return shared.NewInsufficientParamsError(len(obj), 1)
- }
-
- addstr, ok := obj[0].(string)
- if !ok {
- return shared.NewInvalidTypeError("RootHash", "not a string")
- }
- args.RootHash = addstr
-
- addstr, ok = obj[1].(string)
- if !ok {
- return shared.NewInvalidTypeError("Path", "not a string")
- }
- args.Path = addstr
-
- if len(obj) >= 4 {
- addstr, ok = obj[2].(string)
- if ok {
- args.ContentHash = addstr
- }
-
- addstr, ok = obj[3].(string)
- if ok {
- args.ContentType = addstr
- }
- }
-
- return nil
-}
diff --git a/rpc/api/chequebook_js.go b/rpc/api/chequebook_js.go
new file mode 100644
index 0000000000..4254997284
--- /dev/null
+++ b/rpc/api/chequebook_js.go
@@ -0,0 +1,50 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum 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.
+//
+// go-ethereum 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 go-ethereum. If not, see .
+
+package api
+
+const Chequebook_JS = `
+web3._extend({
+ property: 'chequebook',
+ methods:
+ [
+ new web3._extend.Method({
+ name: 'deposit',
+ call: 'chequebook_deposit',
+ params: 1,
+ inputFormatter: [null]
+ }),
+ new web3._extend.Method({
+ name: 'info',
+ call: 'chequebook_info',
+ params: 1,
+ inputFormatter: [null]
+ }),
+ new web3._extend.Method({
+ name: 'cash',
+ call: 'chequebook_cash',
+ params: 1,
+ inputFormatter: [null]
+ }),
+ new web3._extend.Method({
+ name: 'issue',
+ call: 'chequebook_issue',
+ params: 2,
+ inputFormatter: [null, null]
+ }),
+ ]
+});
+`
diff --git a/rpc/api/utils.go b/rpc/api/utils.go
index d52f6b3dc3..336d197f5a 100644
--- a/rpc/api/utils.go
+++ b/rpc/api/utils.go
@@ -64,10 +64,8 @@ var (
"verbosity",
},
"bzz": []string{
+ "version",
"info",
- "issue",
- "cash",
- "deposit",
"register",
"resolve",
"download",
@@ -76,6 +74,13 @@ var (
"put",
"modify",
},
+ "chequebook": []string{
+ "version",
+ "info",
+ "issue",
+ "cash",
+ "deposit",
+ },
"db": []string{
"getString",
"putString",
@@ -129,7 +134,7 @@ var (
"sign",
"syncing",
},
- "miner": []string{
+ "miner ": []string{
"hashrate",
"makeDAG",
"setEtherbase",
@@ -195,7 +200,7 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *no
case shared.AdminApiName:
apis[i] = NewAdminApi(xeth, stack, codec)
case shared.BzzApiName:
- apis[i] = NewBzzApi(stack, codec)
+ case shared.ChequebookApiName:
case shared.DebugApiName:
apis[i] = NewDebugApi(xeth, eth, codec)
case shared.DbApiName:
@@ -229,6 +234,8 @@ func Javascript(name string) string {
return Admin_JS
case shared.BzzApiName:
return Bzz_JS
+ case shared.ChequebookApiName:
+ return Chequebook_JS
case shared.DebugApiName:
return Debug_JS
case shared.DbApiName:
diff --git a/rpc/shared/utils.go b/rpc/shared/utils.go
index 1590265a01..9a6186229e 100644
--- a/rpc/shared/utils.go
+++ b/rpc/shared/utils.go
@@ -19,18 +19,19 @@ package shared
import "strings"
const (
- AdminApiName = "admin"
- BzzApiName = "bzz"
- EthApiName = "eth"
- DbApiName = "db"
- DebugApiName = "debug"
- MergedApiName = "merged"
- MinerApiName = "miner"
- NetApiName = "net"
- ShhApiName = "shh"
- TxPoolApiName = "txpool"
- PersonalApiName = "personal"
- Web3ApiName = "web3"
+ AdminApiName = "admin"
+ BzzApiName = "bzz"
+ ChequebookApiName = "chequebook"
+ EthApiName = "eth"
+ DbApiName = "db"
+ DebugApiName = "debug"
+ MergedApiName = "merged"
+ MinerApiName = "miner"
+ NetApiName = "net"
+ ShhApiName = "shh"
+ TxPoolApiName = "txpool"
+ PersonalApiName = "personal"
+ Web3ApiName = "web3"
JsonRpcVersion = "2.0"
)
@@ -38,7 +39,7 @@ const (
var (
// All API's
AllApis = strings.Join([]string{
- AdminApiName, BzzApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName,
+ AdminApiName, BzzApiName, ChequebookApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName,
ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName,
}, ",")
)
diff --git a/swarm/api/api.go b/swarm/api/api.go
index 11bf4d81ec..ecd18a12a5 100644
--- a/swarm/api/api.go
+++ b/swarm/api/api.go
@@ -1,24 +1,16 @@
package api
import (
- "bufio"
"fmt"
"io"
- "math/big"
- "net/http"
- "os"
- "path/filepath"
"regexp"
"strings"
"sync"
- "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/chequebook"
- "github.com/ethereum/go-ethereum/common/registrar"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/swarm/storage"
)
var (
@@ -27,68 +19,86 @@ var (
domainAndVersion = regexp.MustCompile("[@:;,]+")
)
+type Resolver interface {
+ Resolve(string) (storage.Key, error)
+}
+
/*
Api implements webserver/file system related content storage and retrieval
on top of the dpa
it is the public interface of the dpa which is included in the ethereum stack
*/
type Api struct {
- dpa *storage.DPA
- registrar registrar.VersionedRegistrar
- conf *Config
+ dpa *storage.DPA
+ dns Resolver
}
//the api constructor initialises
-func NewApi(dpa *storage.DPA, registrar registrar.VersionedRegistrar, conf *Config) (self *Api) {
- return &Api{dpa, registrar, conf}
-}
-
-// this should move over to chequebook ipc api
-func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *chequebook.Cheque, err error) {
- return self.conf.Swap.Chequebook().Issue(beneficiary, amount)
-}
-
-func (self *Api) Cash(cheque *chequebook.Cheque) (txhash string, err error) {
- return self.conf.Swap.Chequebook().Cash(cheque)
-}
-
-func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
- return self.conf.Swap.Chequebook().Deposit(amount)
-}
-
-// serialisable info about swarm
-type Info struct {
- *Config
- *chequebook.Params
-}
-
-func (self *Api) Info() *Info {
-
- return &Info{
- Config: self.conf,
- Params: chequebook.ContractParams,
- }
-
-}
-
-// Get uses iterative manifest retrieval and prefix matching
-// to resolve path to content using dpa retrieve
-func (self *Api) Get(bzzpath string) (content []byte, mimeType string, status int, size int, err error) {
- var reader storage.SectionReader
- reader, mimeType, status, err = self.getPath("/" + bzzpath)
- if err != nil {
- return
- }
- content = make([]byte, reader.Size())
- size, err = reader.Read(content)
- if err == io.EOF {
- err = nil
+func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) {
+ self = &Api{
+ dpa: dpa,
+ dns: dns,
}
return
}
-// Put provides singleton manifest creation and optional name registration
-// on top of dpa store
+// DPA reader API
+func (self *Api) Retrieve(key storage.Key) storage.SectionReader {
+ return self.dpa.Retrieve(key)
+}
+
+func (self *Api) Store(data storage.SectionReader, wg *sync.WaitGroup) (key storage.Key, err error) {
+ return self.dpa.Store(data, wg)
+}
+
+// DNS Resolver
+func (self *Api) Resolve(hostPort string) (contentHash storage.Key, err error) {
+ if hashMatcher.MatchString(hostPort) || self.dns == nil {
+ glog.V(logger.Debug).Infof("[BZZ] host is a contentHash: '%v'", contentHash)
+ return storage.Key(common.Hex2Bytes(hostPort)), nil
+ }
+ contentHash, err = self.dns.Resolve(hostPort)
+ if err != nil {
+ err = ErrResolve(err)
+ glog.V(logger.Debug).Infof("[BZZ] DNS error : %v", err)
+ }
+ glog.V(logger.Debug).Infof("[BZZ] host lookup: %v -> %v", err)
+ return
+}
+
+func parse(uri string) (hostPort, path string) {
+ parts := slashes.Split(uri, 3)
+ var i int
+ if len(parts) == 0 {
+ return
+ }
+ // beginning with slash is now optional
+ if len(parts[0]) == 0 {
+ i++
+ }
+ hostPort = parts[i]
+ if len(parts) > i+1 {
+ path = parts[i+1]
+ if len(parts) == 3 {
+ path += "/" + parts[2]
+ }
+ path += "/"
+ }
+ if len(path) > 0 {
+ path = "/" + path
+ }
+ glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
+ return
+}
+
+func (self *Api) parseAndResolve(uri string) (contentHash storage.Key, hostPort, path string, err error) {
+ hostPort, path = parse(uri)
+ //resolving host and port
+ contentHash, err = self.Resolve(hostPort)
+ return
+}
+
+// Put provides singleton manifest creation on top of dpa store
func (self *Api) Put(content, contentType string) (string, error) {
sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content)))
wg := &sync.WaitGroup{}
@@ -106,6 +116,34 @@ func (self *Api) Put(content, contentType string) (string, error) {
return key.String(), nil
}
+// Get uses iterative manifest retrieval and prefix matching
+// to resolve path to content using dpa retrieve
+// it returns a section reader, mimeType, status and an error
+func (self *Api) Get(uri string) (reader storage.SectionReader, mimeType string, status int, err error) {
+
+ key, _, path, err := self.parseAndResolve(uri)
+
+ trie, err := loadManifest(self.dpa, key)
+ if err != nil {
+ glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
+ return
+ }
+
+ glog.V(logger.Debug).Infof("[BZZ] Swarm: getEntry(%s)", path)
+ entry, _ := trie.getEntry(path)
+ if entry != nil {
+ key = common.Hex2Bytes(entry.Hash)
+ status = entry.Status
+ mimeType = entry.ContentType
+ glog.V(logger.Debug).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType)
+ reader = self.dpa.Retrieve(key)
+ } else {
+ err = fmt.Errorf("manifest entry for '%s' not found", path)
+ glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
+ }
+ return
+}
+
func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) {
root := common.Hex2Bytes(rootHash)
trie, err := loadManifest(self.dpa, root)
@@ -130,327 +168,3 @@ func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRoo
}
return trie.hash.String(), nil
}
-
-const maxParallelFiles = 5
-
-// Download replicates the manifest path structure on the local filesystem
-// under localpath
-func (self *Api) Download(bzzpath, localpath string) (err error) {
- lpath, err := filepath.Abs(filepath.Clean(localpath))
- if err != nil {
- return
- }
- err = os.MkdirAll(lpath, os.ModePerm)
- if err != nil {
- return
- }
-
- parts := slashes.Split(bzzpath, 3)
- if len(parts) < 2 {
- return fmt.Errorf("Invalid bzz path")
- }
- hostPort := parts[1]
- var path string
- if len(parts) > 2 {
- path = regularSlashes(parts[2]) + "/"
- }
- glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
-
- //resolving host and port
- var key storage.Key
- key, err = self.Resolve(hostPort)
- if err != nil {
- err = errResolve(err)
- glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err)
- return
- }
-
- trie, err := loadManifest(self.dpa, key)
- if err != nil {
- glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
- return
- }
-
- type downloadListEntry struct {
- key storage.Key
- path string
- }
-
- var list []*downloadListEntry
- var mde, mderr error
-
- prevPath := lpath
- err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize
- key := common.Hex2Bytes(entry.Hash)
- path := lpath + "/" + suffix
- dir := filepath.Dir(path)
- if dir != prevPath {
- mde = os.MkdirAll(dir, os.ModePerm)
- if mde != nil {
- mderr = mde
- }
- prevPath = dir
- }
- if (mde == nil) && (path != dir+"/") {
- list = append(list, &downloadListEntry{key: key, path: path})
- }
- })
- if err == nil {
- err = mderr
- }
-
- cnt := len(list)
- errors := make([]error, cnt)
- done := make(chan bool, maxParallelFiles)
- dcnt := 0
-
- for i, entry := range list {
- if i >= dcnt+maxParallelFiles {
- <-done
- dcnt++
- }
- go func(i int, entry *downloadListEntry, done chan bool) {
- f, err := os.Create(entry.path) // TODO: path separators
- if err == nil {
- reader := self.dpa.Retrieve(entry.key)
- writer := bufio.NewWriter(f)
- _, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors
- err2 := writer.Flush()
- if err == nil {
- err = err2
- }
- err2 = f.Close()
- if err == nil {
- err = err2
- }
- }
-
- errors[i] = err
- done <- true
- }(i, entry, done)
- }
- for dcnt < cnt {
- <-done
- dcnt++
- }
-
- if err != nil {
- return
- }
- for i, _ := range list {
- if errors[i] != nil {
- return errors[i]
- }
- }
- return
-}
-
-// Upload replicates a local directory as a manifest file and uploads it
-// using dpa store
-// TODO: localpath should point to a manifest
-func (self *Api) Upload(lpath, index string) (string, error) {
- var list []*manifestTrieEntry
- localpath, err := filepath.Abs(filepath.Clean(lpath))
- if err != nil {
- return "", err
- }
-
- f, err := os.Open(localpath)
- if err != nil {
- return "", err
- }
- stat, err := f.Stat()
- if err != nil {
- return "", err
- }
-
- var start int
- if stat.IsDir() {
- start = len(localpath)
- glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath)
- err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error {
- if (err == nil) && !info.IsDir() {
- //fmt.Printf("lp %s path %s\n", localpath, path)
- if len(path) <= start {
- return fmt.Errorf("Path is too short")
- }
- if path[:start] != localpath {
- return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath)
- }
- entry := &manifestTrieEntry{
- Path: path,
- }
- list = append(list, entry)
- }
- return err
- })
- if err != nil {
- return "", err
- }
- } else {
- dir := filepath.Dir(localpath)
- start = len(dir)
- if len(localpath) <= start {
- return "", fmt.Errorf("Path is too short")
- }
- if localpath[:start] != dir {
- return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir)
- }
- entry := &manifestTrieEntry{
- Path: localpath,
- }
- list = append(list, entry)
- }
-
- cnt := len(list)
- errors := make([]error, cnt)
- done := make(chan bool, maxParallelFiles)
- dcnt := 0
-
- for i, entry := range list {
- if i >= dcnt+maxParallelFiles {
- <-done
- dcnt++
- }
- go func(i int, entry *manifestTrieEntry, done chan bool) {
- f, err := os.Open(entry.Path)
- if err == nil {
- stat, _ := f.Stat()
- sr := io.NewSectionReader(f, 0, stat.Size())
- wg := &sync.WaitGroup{}
- var hash storage.Key
- hash, err = self.dpa.Store(sr, wg)
- if hash != nil {
- list[i].Hash = hash.String()
- }
- wg.Wait()
- if err == nil {
- first512 := make([]byte, 512)
- fread, _ := sr.ReadAt(first512, 0)
- if fread > 0 {
- mimeType := http.DetectContentType(first512[:fread])
- if filepath.Ext(entry.Path) == ".css" {
- mimeType = "text/css"
- }
- list[i].ContentType = mimeType
- //fmt.Printf("%v %v %v\n", entry.Path, mimeType, filepath.Ext(entry.Path))
- }
- }
- f.Close()
- }
- errors[i] = err
- done <- true
- }(i, entry, done)
- }
- for dcnt < cnt {
- <-done
- dcnt++
- }
-
- trie := &manifestTrie{
- dpa: self.dpa,
- }
- for i, entry := range list {
- if errors[i] != nil {
- return "", errors[i]
- }
- entry.Path = regularSlashes(entry.Path[start:])
- if entry.Path == index {
- ientry := &manifestTrieEntry{
- Path: "",
- Hash: entry.Hash,
- ContentType: entry.ContentType,
- }
- trie.addEntry(ientry)
- }
- trie.addEntry(entry)
- }
-
- err2 := trie.recalcAndStore()
- var hs string
- if err2 == nil {
- hs = trie.hash.String()
- }
- return hs, err2
-}
-
-func (self *Api) Register(sender common.Address, domain string, hash common.Hash) (err error) {
- domainhash := common.BytesToHash(crypto.Sha3([]byte(domain)))
-
- if self.registrar != nil {
- glog.V(logger.Debug).Infof("[BZZ] Swarm: host '%s' (hash: '%v') to be registered as '%v'", domain, domainhash.Hex(), hash.Hex())
- _, err = self.registrar.Registry().SetHashToHash(sender, domainhash, hash)
- } else {
- err = fmt.Errorf("no registry: %v", err)
- }
- return
-}
-
-type errResolve error
-
-func (self *Api) Resolve(hostPort string) (contentHash storage.Key, err error) {
- host := hostPort
- if hashMatcher.MatchString(host) {
- contentHash = storage.Key(common.Hex2Bytes(host))
- glog.V(logger.Debug).Infof("[BZZ] Swarm: host is a contentHash: '%v'", contentHash)
- } else {
- if self.registrar != nil {
- var hash common.Hash
- var version *big.Int
- parts := domainAndVersion.Split(host, 3)
- if len(parts) > 1 && parts[1] != "" {
- host = parts[0]
- version = common.Big(parts[1])
- }
- hostHash := crypto.Sha3Hash([]byte(host))
- hash, err = self.registrar.Resolver(version).HashToHash(hostHash)
- if err != nil {
- err = fmt.Errorf("unable to resolve '%s': %v", hostPort, err)
- }
- contentHash = storage.Key(hash.Bytes())
- glog.V(logger.Debug).Infof("[BZZ] Swarm: resolve host '%s' to contentHash: '%v'", hostPort, contentHash)
- } else {
- err = fmt.Errorf("no resolver '%s': %v", hostPort, err)
- }
- }
- return
-}
-
-func (self *Api) getPath(uri string) (reader storage.SectionReader, mimeType string, status int, err error) {
- parts := slashes.Split(uri, 3)
- hostPort := parts[1]
- var path string
- if len(parts) > 2 {
- path = parts[2]
- }
- glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
-
- //resolving host and port
- var key storage.Key
- key, err = self.Resolve(hostPort)
- if err != nil {
- err = errResolve(err)
- glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err)
- return
- }
-
- trie, err := loadManifest(self.dpa, key)
- if err != nil {
- glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
- return
- }
-
- glog.V(logger.Debug).Infof("[BZZ] Swarm: getEntry(%s)", path)
- entry, _ := trie.getEntry(path)
- if entry != nil {
- key = common.Hex2Bytes(entry.Hash)
- status = entry.Status
- mimeType = entry.ContentType
- glog.V(logger.Debug).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType)
- reader = self.dpa.Retrieve(key)
- } else {
- err = fmt.Errorf("manifest entry for '%s' not found", path)
- glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
- }
- return
-}
diff --git a/swarm/api/api_test.go b/swarm/api/api_test.go
index 81156afcd8..7db209c9d4 100644
--- a/swarm/api/api_test.go
+++ b/swarm/api/api_test.go
@@ -1,205 +1,86 @@
package api
import (
- "bytes"
+ // "bytes"
"io/ioutil"
"os"
- "path"
- "runtime"
"testing"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/swarm/storage"
)
-//TODO: add tests for resolver/registrar
-// will most likely be its own package under service?
-
-var (
- testDir string
-)
-
-func init() {
- _, filename, _, _ := runtime.Caller(1)
- testDir = path.Join(path.Dir(filename), "../test")
-}
-
-func testApi() (api *Api, err error) {
+func testApi(t *testing.T, f func(*Api)) {
datadir, err := ioutil.TempDir("", "bzz-test")
if err != nil {
- return nil, err
+ t.Fatalf("unable to create temp dir: %v", err)
}
os.RemoveAll(datadir)
+ defer os.RemoveAll(datadir)
dpa, err := storage.NewLocalDPA(datadir)
if err != nil {
return
}
- prvkey, _ := crypto.GenerateKey()
+ api := NewApi(dpa, nil)
+ dpa.Start()
+ f(api)
+ dpa.Stop()
+}
- config, err := NewConfig(datadir, common.Address{}, prvkey)
- if err != nil {
- return
+type testResponse struct {
+ reader storage.SectionReader
+ *Response
+}
+
+func checkResponse(t *testing.T, resp *testResponse, exp *Response) {
+
+ if resp.MimeType != exp.MimeType {
+ t.Errorf("incorrect mimeType. expected '%s', got '%s'", exp.MimeType, resp.MimeType)
}
- api = NewApi(dpa, nil, config)
- api.dpa.Start()
+ if resp.Status != exp.Status {
+ t.Errorf("incorrect status. expected '%d', got '%d'", exp.Status, resp.Status)
+ }
+ if resp.Size != exp.Size {
+ t.Errorf("incorrect size. expected '%d', got '%d'", exp.Size, resp.Size)
+ }
+ if resp.reader != nil {
+ content := make([]byte, resp.Size)
+ read, _ := resp.reader.Read(content)
+ if int64(read) != exp.Size {
+ t.Errorf("incorrect content length. expected '%s...', got '%s...'", read, exp.Size)
+ }
+ resp.Content = string(content)
+ }
+ if resp.Content != exp.Content {
+ // if !bytes.Equal(resp.Content, exp.Content) {
+ t.Errorf("incorrect content. expected '%s...', got '%s...'", string(exp.Content), string(resp.Content))
+ }
+}
- return
+// func expResponse(content []byte, mimeType string, status int) *Response {
+func expResponse(content string, mimeType string, status int) *Response {
+ return &Response{mimeType, status, int64(len(content)), content}
+}
+
+// func testGet(t *testing.T, api *Api, bzzhash string) *testResponse {
+func testGet(t *testing.T, api *Api, bzzhash string) *testResponse {
+ reader, mimeType, status, err := api.Get(bzzhash)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ return &testResponse{reader, &Response{mimeType, status, reader.Size(), ""}}
+ // return &testResponse{reader, &Response{mimeType, status, reader.Size(), nil}}
}
func TestApiPut(t *testing.T) {
- api, err := testApi()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- defer api.dpa.Stop()
- expContent := "hello"
- expMimeType := "text/plain"
- expStatus := 0
- expSize := len(expContent)
- bzzhash, err := api.Put(expContent, expMimeType)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- testGet(t, api, bzzhash, []byte(expContent), expMimeType, expStatus, expSize)
-}
-
-func testGet(t *testing.T, api *Api, bzzhash string, expContent []byte, expMimeType string, expStatus int, expSize int) {
- content, mimeType, status, size, err := api.Get(bzzhash)
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- if !bytes.Equal(content, expContent) {
- t.Errorf("incorrect content. expected '%s...', got '%s...'", string(expContent), string(content))
- }
- if mimeType != expMimeType {
- t.Errorf("incorrect mimeType. expected '%s', got '%s'", expMimeType, mimeType)
- }
- if status != expStatus {
- t.Errorf("incorrect status. expected '%d', got '%d'", expStatus, status)
- }
- if size != expSize {
- t.Errorf("incorrect size. expected '%d', got '%d'", expSize, size)
- }
-}
-
-func TestApiDirUpload(t *testing.T) {
- t.Skip("FIXME")
- api, err := testApi()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err := api.Upload(path.Join(testDir, "test0"), "")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
- testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202)
-
- content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css"))
- testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132)
-
- content, err = ioutil.ReadFile(path.Join(testDir, "test0", "img", "logo.png"))
- testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "image/png", 0, 18136)
-
- _, _, _, _, err = api.Get(bzzhash)
- if err == nil {
- t.Errorf("expected error: %v", err)
- }
-}
-
-func TestApiDirUploadModify(t *testing.T) {
- t.Skip("FIXME")
- api, err := testApi()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err := api.Upload(path.Join(testDir, "test0"), "")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- bzzhash, err = api.Modify(bzzhash, "index.html", "", "")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err = api.Modify(bzzhash, "index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err = api.Modify(bzzhash, "img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
- testGet(t, api, path.Join(bzzhash, "index2.html"), content, "text/html; charset=utf-8", 0, 202)
- testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "text/html; charset=utf-8", 0, 202)
-
- content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css"))
- testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132)
-
- _, _, _, _, err = api.Get(bzzhash)
- if err == nil {
- t.Errorf("expected error: %v", err)
- }
-}
-
-func TestApiDirUploadWithRootFile(t *testing.T) {
- api, err := testApi()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err := api.Upload(path.Join(testDir, "test0"), "index.html")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
- testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202)
-}
-
-func TestApiFileUpload(t *testing.T) {
- api, err := testApi()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
- testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202)
-}
-
-func TestApiFileUploadWithRootFile(t *testing.T) {
- api, err := testApi()
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
- bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "index.html")
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html"))
- testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202)
+ testApi(t, func(api *Api) {
+ content := "hello"
+ exp := expResponse(content, "text/plain", 0)
+ // exp := expResponse([]byte(content), "text/plain", 0)
+ bzzhash, err := api.Put(content, exp.MimeType)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ resp := testGet(t, api, bzzhash)
+ checkResponse(t, resp, exp)
+ })
}
diff --git a/swarm/api/config_test.go b/swarm/api/config_test.go
index 44bb484667..9bed72d23d 100644
--- a/swarm/api/config_test.go
+++ b/swarm/api/config_test.go
@@ -91,6 +91,7 @@ func TestConfigWriteRead(t *testing.T) {
t.Fatalf("default config file cannot be read: %v", err)
}
exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1)
+ exp = strings.Replace(exp, "\\", "\\\\", -1)
if string(data) != exp {
t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data))
diff --git a/swarm/api/dns.go b/swarm/api/dns.go
new file mode 100644
index 0000000000..c26722bca1
--- /dev/null
+++ b/swarm/api/dns.go
@@ -0,0 +1,58 @@
+package api
+
+import (
+ "fmt"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/registrar"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/swarm/storage"
+)
+
+// swarm domain name registry and resolver
+// the DNS instance can be directly wrapped in rpc.Api
+type DNS struct {
+ registrar registrar.VersionedRegistrar
+}
+
+func NewDNS(registrar registrar.VersionedRegistrar) *DNS {
+ return &DNS{registrar}
+}
+
+// Register involves sending a transaction, sender is an account with funds
+// the same account is used to register the authors of commits
+func (self *DNS) Register(sender common.Address, domain string, hash common.Hash) (err error) {
+ domainhash := common.BytesToHash(crypto.Sha3([]byte(domain)))
+
+ if self.registrar != nil {
+ glog.V(logger.Debug).Infof("[DNR]: host '%s' (hash: '%v') to be registered as '%v'", domain, domainhash.Hex(), hash.Hex())
+ _, err = self.registrar.Registry().SetHashToHash(sender, domainhash, hash)
+ } else {
+ err = fmt.Errorf("no registry: %v", err)
+ }
+ return
+}
+
+type ErrResolve error
+
+func (self *DNS) Resolve(hostPort string) (contentHash storage.Key, err error) {
+ host := hostPort
+ var hash common.Hash
+ var version *big.Int
+ parts := domainAndVersion.Split(host, 3)
+ if len(parts) > 1 && parts[1] != "" {
+ host = parts[0]
+ version = common.Big(parts[1])
+ }
+ hostHash := crypto.Sha3Hash([]byte(host))
+ hash, err = self.registrar.Resolver(version).HashToHash(hostHash)
+ if err != nil {
+ err = fmt.Errorf("unable to resolve '%s': %v", hostPort, err)
+ }
+ contentHash = storage.Key(hash.Bytes())
+ glog.V(logger.Debug).Infof("[DNR] resolve host '%s' to contentHash: '%v'", hostPort, contentHash)
+ return
+}
diff --git a/swarm/api/filesystem.go b/swarm/api/filesystem.go
new file mode 100644
index 0000000000..eddd72dd0b
--- /dev/null
+++ b/swarm/api/filesystem.go
@@ -0,0 +1,258 @@
+package api
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/swarm/storage"
+)
+
+const maxParallelFiles = 5
+
+type FileSystem struct {
+ api *Api
+}
+
+func NewFileSystem(api *Api) *FileSystem {
+ return &FileSystem{api}
+}
+
+// Upload replicates a local directory as a manifest file and uploads it
+// using dpa store
+// TODO: localpath should point to a manifest
+func (self *FileSystem) Upload(lpath, index string) (string, error) {
+ var list []*manifestTrieEntry
+ localpath, err := filepath.Abs(filepath.Clean(lpath))
+ if err != nil {
+ return "", err
+ }
+
+ f, err := os.Open(localpath)
+ if err != nil {
+ return "", err
+ }
+ stat, err := f.Stat()
+ if err != nil {
+ return "", err
+ }
+
+ var start int
+ if stat.IsDir() {
+ start = len(localpath)
+ glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath)
+ err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error {
+ if (err == nil) && !info.IsDir() {
+ //fmt.Printf("lp %s path %s\n", localpath, path)
+ if len(path) <= start {
+ return fmt.Errorf("Path is too short")
+ }
+ if path[:start] != localpath {
+ return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath)
+ }
+ entry := &manifestTrieEntry{
+ Path: path,
+ }
+ list = append(list, entry)
+ }
+ return err
+ })
+ if err != nil {
+ return "", err
+ }
+ } else {
+ dir := filepath.Dir(localpath)
+ start = len(dir)
+ if len(localpath) <= start {
+ return "", fmt.Errorf("Path is too short")
+ }
+ if localpath[:start] != dir {
+ return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir)
+ }
+ entry := &manifestTrieEntry{
+ Path: localpath,
+ }
+ list = append(list, entry)
+ }
+
+ cnt := len(list)
+ errors := make([]error, cnt)
+ done := make(chan bool, maxParallelFiles)
+ dcnt := 0
+
+ for i, entry := range list {
+ if i >= dcnt+maxParallelFiles {
+ <-done
+ dcnt++
+ }
+ go func(i int, entry *manifestTrieEntry, done chan bool) {
+ f, err := os.Open(entry.Path)
+ if err == nil {
+ stat, _ := f.Stat()
+ sr := io.NewSectionReader(f, 0, stat.Size())
+ wg := &sync.WaitGroup{}
+ var hash storage.Key
+ hash, err = self.api.dpa.Store(sr, wg)
+ if hash != nil {
+ list[i].Hash = hash.String()
+ }
+ wg.Wait()
+ if err == nil {
+ first512 := make([]byte, 512)
+ fread, _ := sr.ReadAt(first512, 0)
+ if fread > 0 {
+ mimeType := http.DetectContentType(first512[:fread])
+ if filepath.Ext(entry.Path) == ".css" {
+ mimeType = "text/css"
+ }
+ list[i].ContentType = mimeType
+ //fmt.Printf("%v %v %v\n", entry.Path, mimeType, filepath.Ext(entry.Path))
+ }
+ }
+ f.Close()
+ }
+ errors[i] = err
+ done <- true
+ }(i, entry, done)
+ }
+ for dcnt < cnt {
+ <-done
+ dcnt++
+ }
+
+ trie := &manifestTrie{
+ dpa: self.api.dpa,
+ }
+ for i, entry := range list {
+ if errors[i] != nil {
+ return "", errors[i]
+ }
+ entry.Path = RegularSlashes(entry.Path[start:])
+ if entry.Path == index {
+ ientry := &manifestTrieEntry{
+ Path: "",
+ Hash: entry.Hash,
+ ContentType: entry.ContentType,
+ }
+ trie.addEntry(ientry)
+ }
+ trie.addEntry(entry)
+ }
+
+ err2 := trie.recalcAndStore()
+ var hs string
+ if err2 == nil {
+ hs = trie.hash.String()
+ }
+ return hs, err2
+}
+
+// Download replicates the manifest path structure on the local filesystem
+// under localpath
+func (self *FileSystem) Download(bzzpath, localpath string) error {
+ lpath, err := filepath.Abs(filepath.Clean(localpath))
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(lpath, os.ModePerm)
+ if err != nil {
+ return err
+ }
+
+ //resolving host and port
+ key, _, path, err := self.api.parseAndResolve(bzzpath)
+ if err != nil {
+ return err
+ }
+ // if len(path) > 0 {
+ // path += "/"
+ // }
+
+ trie, err := loadManifest(self.api.dpa, key)
+ if err != nil {
+ glog.V(logger.Debug).Infof("[BZZ] fs.Download: loadManifestTrie error: %v", err)
+ return err
+ }
+
+ type downloadListEntry struct {
+ key storage.Key
+ path string
+ }
+
+ var list []*downloadListEntry
+ var mde, mderr error
+
+ prevPath := lpath
+ err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize
+ glog.V(logger.Detail).Infof("[BZZ] fs.Download: %#v", entry)
+
+ key := common.Hex2Bytes(entry.Hash)
+ path := lpath + "/" + suffix
+ dir := filepath.Dir(path)
+ if dir != prevPath {
+ mde = os.MkdirAll(dir, os.ModePerm)
+ if mde != nil {
+ mderr = mde
+ }
+ prevPath = dir
+ }
+ if (mde == nil) && (path != dir+"/") {
+ list = append(list, &downloadListEntry{key: key, path: path})
+ }
+ })
+ if err == nil {
+ err = mderr
+ }
+
+ cnt := len(list)
+ errors := make([]error, cnt)
+ done := make(chan bool, maxParallelFiles)
+ dcnt := 0
+
+ for i, entry := range list {
+ if i >= dcnt+maxParallelFiles {
+ <-done
+ dcnt++
+ }
+ go func(i int, entry *downloadListEntry, done chan bool) {
+ f, err := os.Create(entry.path) // TODO: path separators
+ if err == nil {
+ reader := self.api.dpa.Retrieve(entry.key)
+ writer := bufio.NewWriter(f)
+ _, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors
+ err2 := writer.Flush()
+ if err == nil {
+ err = err2
+ }
+ err2 = f.Close()
+ if err == nil {
+ err = err2
+ }
+ }
+
+ errors[i] = err
+ done <- true
+ }(i, entry, done)
+ }
+ for dcnt < cnt {
+ <-done
+ dcnt++
+ }
+
+ if err != nil {
+ return err
+ }
+ for i, _ := range list {
+ if errors[i] != nil {
+ return errors[i]
+ }
+ }
+ return err
+}
diff --git a/swarm/api/filesystem_test.go b/swarm/api/filesystem_test.go
new file mode 100644
index 0000000000..174d8833a1
--- /dev/null
+++ b/swarm/api/filesystem_test.go
@@ -0,0 +1,176 @@
+package api
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+ "runtime"
+ "testing"
+)
+
+var (
+ testDir string
+ testDownloadDir string
+)
+
+func init() {
+ _, filename, _, _ := runtime.Caller(1)
+ testDir = path.Join(path.Dir(filename), "../test")
+ testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test")
+}
+
+func testFileSystem(t *testing.T, f func(*FileSystem)) {
+ testApi(t, func(api *Api) {
+ f(NewFileSystem(api))
+ })
+}
+
+func readPath(t *testing.T, parts ...string) string {
+ // func readPath(t *testing.T, parts ...string) []byte {
+ file := path.Join(parts...)
+ content, err := ioutil.ReadFile(file)
+ if err != nil {
+ t.Fatalf("unexpected error reading '%v': %v", file, err)
+ }
+ return string(content)
+}
+
+func TestApiDirUpload0(t *testing.T) {
+ // t.Skip("FIXME")
+ testFileSystem(t, func(fs *FileSystem) {
+ api := fs.api
+ bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ content := readPath(t, testDir, "test0", "index.html")
+ resp := testGet(t, api, bzzhash+"/index.html")
+ exp := expResponse(content, "text/html; charset=utf-8", 0)
+ checkResponse(t, resp, exp)
+
+ content = readPath(t, testDir, "test0", "index.css")
+ resp = testGet(t, api, bzzhash+"/index.css")
+ exp = expResponse(content, "text/css", 0)
+ checkResponse(t, resp, exp)
+
+ content = readPath(t, testDir, "test0", "img", "logo.png")
+ resp = testGet(t, api, bzzhash+"/img/logo.png")
+ exp = expResponse(content, "image/png", 0)
+
+ _, _, _, err = api.Get(bzzhash)
+ if err == nil {
+ t.Fatalf("expected error: %v", err)
+ }
+
+ downloadDir := path.Join(testDownloadDir, "test0")
+ os.RemoveAll(downloadDir)
+ defer os.RemoveAll(downloadDir)
+ err = fs.Download(bzzhash, downloadDir)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ newbzzhash, err := fs.Upload(downloadDir, "")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if bzzhash != newbzzhash {
+ t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash)
+ }
+
+ })
+}
+
+func TestApiDirUploadModify(t *testing.T) {
+ // t.Skip("FIXME")
+ testFileSystem(t, func(fs *FileSystem) {
+ api := fs.api
+ bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+
+ bzzhash, err = api.Modify(bzzhash, "index.html", "", "")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+ bzzhash, err = api.Modify(bzzhash, "index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+ bzzhash, err = api.Modify(bzzhash, "img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+
+ content := readPath(t, testDir, "test0", "index.html")
+ resp := testGet(t, api, bzzhash+"/index2.html")
+ exp := expResponse(content, "text/html; charset=utf-8", 0)
+ checkResponse(t, resp, exp)
+
+ resp = testGet(t, api, bzzhash+"/img/logo.png")
+ exp = expResponse(content, "text/html; charset=utf-8", 0)
+ checkResponse(t, resp, exp)
+
+ content = readPath(t, testDir, "test0", "index.css")
+ resp = testGet(t, api, bzzhash+"/index.css")
+ exp = expResponse(content, "text/css", 0)
+
+ _, _, _, err = api.Get(bzzhash)
+ if err == nil {
+ t.Errorf("expected error: %v", err)
+ }
+ })
+}
+
+func TestApiDirUploadWithRootFile(t *testing.T) {
+ testFileSystem(t, func(fs *FileSystem) {
+ api := fs.api
+ bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "index.html")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+
+ content := readPath(t, testDir, "test0", "index.html")
+ resp := testGet(t, api, bzzhash)
+ exp := expResponse(content, "text/html; charset=utf-8", 0)
+ checkResponse(t, resp, exp)
+ })
+}
+
+func TestApiFileUpload(t *testing.T) {
+ testFileSystem(t, func(fs *FileSystem) {
+ api := fs.api
+ bzzhash, err := fs.Upload(path.Join(testDir, "test0", "index.html"), "")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+
+ content := readPath(t, testDir, "test0", "index.html")
+ resp := testGet(t, api, bzzhash+"/index.html")
+ exp := expResponse(content, "text/html; charset=utf-8", 0)
+ checkResponse(t, resp, exp)
+ })
+}
+
+func TestApiFileUploadWithRootFile(t *testing.T) {
+ testFileSystem(t, func(fs *FileSystem) {
+ api := fs.api
+ bzzhash, err := fs.Upload(path.Join(testDir, "test0", "index.html"), "index.html")
+ if err != nil {
+ t.Errorf("unexpected error: %v", err)
+ return
+ }
+
+ content := readPath(t, testDir, "test0", "index.html")
+ resp := testGet(t, api, bzzhash)
+ exp := expResponse(content, "text/html; charset=utf-8", 0)
+ checkResponse(t, resp, exp)
+ })
+}
diff --git a/swarm/api/http/roundtripper.go b/swarm/api/http/roundtripper.go
new file mode 100644
index 0000000000..da6a63a255
--- /dev/null
+++ b/swarm/api/http/roundtripper.go
@@ -0,0 +1,49 @@
+package http
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+)
+
+/*
+http roundtripper to register for bzz url scheme
+see https://github.com/ethereum/go-ethereum/issues/2040
+Usage:
+
+import (
+ "github.com/ethereum/go-ethereum/common/httpclient"
+ "github.com/ethereum/go-ethereum/swarm/api/http"
+)
+client := httpclient.New()
+// for (private) swarm proxy running locally
+client.RegisterScheme("bzz", &http.RoundTripper{Port: port})
+// for public swarm gateway
+client.RegisterScheme(scheme, &http.RoundTripper{Host: host, Port: port})
+
+The port you give the Roundtripper is the port the swarm proxy is listening on.
+If Host is left empty, localhost is assumed.
+
+Using a public gateway, the above few lines gives you the leanest
+bzz-scheme aware read-only http client. You really only ever need this
+if you need go-native swarm access to bzz addresses, e.g.,
+github.com/ethereum/go-ethereum/common/natspec
+
+*/
+
+type RoundTripper struct {
+ Host string
+ Port string
+}
+
+func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
+ host := self.Host
+ if len(host) == 0 {
+ host = "localhost"
+ }
+ url := fmt.Sprintf("http://%s:%s/%s/%s", host, self.Port, req.URL.Host, req.URL.Path)
+ glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)
+ return http.Get(url)
+}
diff --git a/swarm/api/roundtripper_test.go b/swarm/api/http/roundtripper_test.go
similarity index 95%
rename from swarm/api/roundtripper_test.go
rename to swarm/api/http/roundtripper_test.go
index e0b15658dc..51bcd2e41c 100644
--- a/swarm/api/roundtripper_test.go
+++ b/swarm/api/http/roundtripper_test.go
@@ -1,4 +1,4 @@
-package api
+package http
import (
"io/ioutil"
@@ -22,7 +22,7 @@ func TestRoundTripper(t *testing.T) {
})
go http.ListenAndServe(":8600", serveMux)
- rt := &RoundTripper{"8600"}
+ rt := &RoundTripper{Port: "8600"}
client := httpclient.New("/")
client.RegisterProtocol("bzz", rt)
diff --git a/swarm/api/http.go b/swarm/api/http/server.go
similarity index 91%
rename from swarm/api/http.go
rename to swarm/api/http/server.go
index b1cd039cce..efa3651652 100644
--- a/swarm/api/http.go
+++ b/swarm/api/http/server.go
@@ -1,7 +1,7 @@
/*
A simple http server interface to Swarm
*/
-package api
+package http
import (
"bytes"
@@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
+ "github.com/ethereum/go-ethereum/swarm/api"
)
const (
@@ -41,7 +42,7 @@ type sequentialReader struct {
// https://github.com/atom/electron/blob/master/docs/api/protocol.md
// starts up http server
-func StartHttpServer(api *Api, port string) {
+func StartHttpServer(api *api.Api, port string) {
serveMux := http.NewServeMux()
serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handler(w, r, api)
@@ -50,7 +51,7 @@ func StartHttpServer(api *Api, port string) {
glog.V(logger.Info).Infof("[BZZ] Swarm HTTP proxy started on localhost:%s", port)
}
-func handler(w http.ResponseWriter, r *http.Request, api *Api) {
+func handler(w http.ResponseWriter, r *http.Request, a *api.Api) {
requestURL := r.URL
// This is wrong
// if requestURL.Host == "" {
@@ -78,7 +79,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
switch {
case r.Method == "POST" || r.Method == "PUT":
- key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{
+ key, err := a.Store(io.NewSectionReader(&sequentialReader{
reader: r.Body,
ahead: make(map[int64]chan bool),
}, 0, r.ContentLength), nil)
@@ -102,11 +103,11 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest)
return
} else {
- path = regularSlashes(path)
+ path = api.RegularSlashes(path)
mime := r.Header.Get("Content-Type")
// TODO proper root hash separation
glog.V(logger.Debug).Infof("[BZZ] Modify '%s' to store %v as '%s'.", path, key.Log(), mime)
- newKey, err := api.Modify(path[:64], path[65:], common.Bytes2Hex(key), mime)
+ newKey, err := a.Modify(path[:64], path[65:], common.Bytes2Hex(key), mime)
if err == nil {
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
w.Header().Set("Content-Type", "text/plain")
@@ -122,9 +123,9 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest)
return
} else {
- path = regularSlashes(path)
+ path = api.RegularSlashes(path)
glog.V(logger.Debug).Infof("[BZZ] Delete '%s'.", path)
- newKey, err := api.Modify(path[:64], path[65:], "", "")
+ newKey, err := a.Modify(path[:64], path[65:], "", "")
if err == nil {
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
w.Header().Set("Content-Type", "text/plain")
@@ -138,7 +139,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
path = trailingSlashes.ReplaceAllString(path, "")
if raw {
// resolving host
- key, err := api.Resolve(path)
+ key, err := a.Resolve(path)
if err != nil {
glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -146,7 +147,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
}
// retrieving content
- reader := api.dpa.Retrieve(key)
+ reader := a.Retrieve(key)
glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size())
// setting mime type
@@ -165,10 +166,9 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
glog.V(logger.Debug).Infof("[BZZ] Swarm: Structured GET request '%s' received.", uri)
- // call to api.getPath on uri
- reader, mimeType, status, err := api.getPath(path)
+ reader, mimeType, status, err := a.Get(path)
if err != nil {
- if _, ok := err.(errResolve); ok {
+ if _, ok := err.(api.ErrResolve); ok {
glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
status = http.StatusBadRequest
} else {
diff --git a/swarm/api/manifest.go b/swarm/api/manifest.go
index c1781ab7d3..42942a0213 100644
--- a/swarm/api/manifest.go
+++ b/swarm/api/manifest.go
@@ -290,7 +290,7 @@ func (self *manifestTrie) findPrefixOf(path string) (entry *manifestTrieEntry, p
// file system manifest always contains regularized paths
// no leading or trailing slashes, only single slashes inside
-func regularSlashes(path string) (res string) {
+func RegularSlashes(path string) (res string) {
for i := 0; i < len(path); i++ {
if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) {
res = res + path[i:i+1]
@@ -303,7 +303,7 @@ func regularSlashes(path string) (res string) {
}
func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) {
- path := regularSlashes(spath)
+ path := RegularSlashes(spath)
var pos int
entry, pos = self.findPrefixOf(path)
return entry, path[:pos]
diff --git a/swarm/api/roundtripper.go b/swarm/api/roundtripper.go
deleted file mode 100644
index 3dad7d04ef..0000000000
--- a/swarm/api/roundtripper.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package api
-
-import (
- "fmt"
- "net/http"
-
- "github.com/ethereum/go-ethereum/logger"
- "github.com/ethereum/go-ethereum/logger/glog"
-
- // "github.com/ethereum/go-ethereum/common/httpclient"
- // "github.com/ethereum/go-ethereum/jsre"
-)
-
-type RoundTripper struct {
- Port string
-}
-
-func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) {
- url := fmt.Sprintf("http://localhost:%s/%s/%s", self.Port, req.URL.Host, req.URL.Path)
- glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)
- return http.Get(url)
-}
diff --git a/swarm/api/storage.go b/swarm/api/storage.go
new file mode 100644
index 0000000000..6563c75ae0
--- /dev/null
+++ b/swarm/api/storage.go
@@ -0,0 +1,56 @@
+package api
+
+import (
+ // "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/logger"
+ "github.com/ethereum/go-ethereum/logger/glog"
+ // "github.com/ethereum/go-ethereum/swarm/storage"
+)
+
+type Response struct {
+ MimeType string
+ Status int
+ Size int64
+ // Content []byte
+ Content string
+}
+
+// implements a service
+type Storage struct {
+ api *Api
+}
+
+func NewStorage(api *Api) *Storage {
+ return &Storage{api}
+}
+
+// Put uploads the content to the swarm with a simple manifest speficying
+// its content type
+func (self *Storage) Put(content, contentType string) (string, error) {
+ return self.api.Put(content, contentType)
+}
+
+// Get retrieves the content from bzzpath and reads the response in full
+// It returns the Response object, which serialises containing the
+// response body as the value of the Content field
+// NOTE: if error is non-nil, sResponse may still have partial content
+// the actual size of which is given in len(resp.Content), while the expected
+// size is resp.Size
+func (self *Storage) Get(bzzpath string) (*Response, error) {
+ reader, mimeType, status, err := self.api.Get(bzzpath)
+ if err != nil {
+ return nil, err
+ }
+ expsize := reader.Size()
+ body := make([]byte, expsize)
+ size, err := reader.Read(body)
+ if int64(size) == expsize {
+ err = nil
+ }
+ glog.V(logger.Detail).Infof("body: %s", body[:size])
+ return &Response{mimeType, status, expsize, string(body[:size])}, err
+}
+
+func (self *Storage) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) {
+ return self.api.Modify(rootHash, path, contentHash, contentType)
+}
diff --git a/swarm/api/storage_test.go b/swarm/api/storage_test.go
new file mode 100644
index 0000000000..44fa7636dc
--- /dev/null
+++ b/swarm/api/storage_test.go
@@ -0,0 +1,33 @@
+package api
+
+import (
+ "testing"
+)
+
+func testStorage(t *testing.T, f func(*Storage)) {
+ testApi(t, func(api *Api) {
+ f(NewStorage(api))
+ })
+}
+
+func TestStoragePutGet(t *testing.T) {
+ testStorage(t, func(api *Storage) {
+ content := "hello"
+ exp := expResponse(content, "text/plain", 0)
+ // exp := expResponse([]byte(content), "text/plain", 0)
+ bzzhash, err := api.Put(content, exp.MimeType)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ // to check put against the Api#Get
+ resp0 := testGet(t, api.api, bzzhash)
+ checkResponse(t, resp0, exp)
+
+ // check storage#Get
+ resp, err := api.Get(bzzhash)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ checkResponse(t, &testResponse{nil, resp}, exp)
+ })
+}
diff --git a/swarm/swarm.go b/swarm/swarm.go
index d7edbfbcd8..f323ffd368 100644
--- a/swarm/swarm.go
+++ b/swarm/swarm.go
@@ -15,15 +15,23 @@ import (
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
+ rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/ethereum/go-ethereum/swarm/api"
+ httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/network"
"github.com/ethereum/go-ethereum/swarm/storage"
)
+const (
+ Namespace = "bzz"
+ Version = "0.1" // versioning reflect POC and release versions
+)
+
// the swarm stack
type Swarm struct {
config *api.Config // swarm configuration
api *api.Api // high level api layer (fs/manifest)
+ dns api.Resolver // DNS registrar
dbAccess *network.DbAccess // access to local chunk db iterator and storage counter
storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends
dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support
@@ -78,9 +86,9 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
// setup cloud storage internal access layer
self.storage = storage.NewNetStore(hash, lstore, cloud, config.StoreParams)
- glog.V(logger.Debug).Infof("[BZZ] -> Level 0: swarm net store shared access layer to Swarm Chunk Store")
+ glog.V(logger.Debug).Infof("[BZZ] -> swarm net store shared access layer to Swarm Chunk Store")
- // set up Depo (storage handler = remote cloud storage access layer)
+ // set up Depo (storage handler = cloud storage access layer for incoming remote requests)
self.depo = network.NewDepo(hash, lstore, self.storage)
glog.V(logger.Debug).Infof("[BZZ] -> REmote Access to CHunks")
@@ -89,14 +97,17 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
glog.V(logger.Debug).Infof("[BZZ] -> Local Access to Swarm")
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams)
- glog.V(logger.Debug).Infof("[BZZ] -> Level 1: Document/File API")
+ glog.V(logger.Debug).Infof("[BZZ] -> Content Store API")
// set up high level api
backend := api.NewEthApi(ethereum)
backend.UpdateState()
- self.api = api.NewApi(self.dpa, ethreg.New(backend), self.config)
+ self.dns = api.NewDNS(ethreg.New(backend))
+ glog.V(logger.Debug).Infof("[BZZ] -> Swarm Domain Registrar")
+
+ self.api = api.NewApi(self.dpa, self.dns)
// Manifests for Smart Hosting
- glog.V(logger.Debug).Infof("[BZZ] -> Level 2: Collection/Directory API")
+ glog.V(logger.Debug).Infof("[BZZ] -> Web3 virtual server API")
// set chequebook
if swapEnabled {
@@ -143,7 +154,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
// start swarm http proxy server
if self.config.Port != "" {
- go api.StartHttpServer(self.api, self.config.Port)
+ go httpapi.StartHttpServer(self.api, self.config.Port)
}
glog.V(logger.Debug).Infof("[BZZ] Swarm http proxy started on port: %v", self.config.Port)
@@ -154,7 +165,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
"bzz": self.config.Port,
}
for scheme, port := range schemes {
- self.client.RegisterScheme(scheme, &api.RoundTripper{Port: port})
+ self.client.RegisterScheme(scheme, &httpapi.RoundTripper{Port: port})
}
glog.V(logger.Debug).Infof("[BZZ] Swarm protocol handlers registered for url schemes: %v", schemes)
@@ -182,8 +193,21 @@ func (self *Swarm) Protocols() []p2p.Protocol {
return []p2p.Protocol{proto}
}
-func (self *Swarm) Api() *api.Api {
- return self.api
+// implements node.Service
+// Apis returns the RPC Api descriptors the Swarm implementation offers
+func (self *Swarm) APIs() []rpc.API {
+ return []rpc.API{
+ // public APIs.
+ rpc.API{Namespace, Version, api.NewStorage(self.api), true},
+ rpc.API{Namespace, Version, self.dns, true},
+ rpc.API{Namespace, Version, &Info{self.config, chequebook.ContractParams}, true},
+ // admin APIs
+ rpc.API{Namespace, Version, api.NewFileSystem(self.api), false},
+ // rpc.API{Namespace, Version, test.New(self), false},
+ // rpc.API{Namespace, Version, api.NewAdmin(self), false},
+ // TODO: external apis exposed
+ rpc.API{"chequebook", chequebook.Version, chequebook.NewApi(self.config.Swap.Chequebook()), true},
+ }
}
// Backend interface implemented by eth or JSON-IPC client
@@ -223,9 +247,19 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {
}
self = &Swarm{
- api: api.NewApi(dpa, nil, config),
+ api: api.NewApi(dpa, nil),
config: config,
}
return
}
+
+// serialisable info about swarm
+type Info struct {
+ *api.Config
+ *chequebook.Params
+}
+
+func (self *Info) Info() *Info {
+ return self
+}