integrate RPC v2 via develop

* integrate #2035 via latest develop. fixes #2041
* refactor apis
* fix tests for windows, add simple download test
* separate dns api and chequebook api
* http proxy api in separate subpackage
* remove legacy rpc code partially, adding only legacy to make v2 work
* TODO: add dns tests see #2048
* TODO: further refactor  due to #2040
This commit is contained in:
zelig 2015-12-14 21:34:17 +00:00
parent 3ab152ebaf
commit 9d2605ee32
21 changed files with 942 additions and 1300 deletions

View file

@ -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
// }
})
}

29
common/chequebook/api.go Normal file
View file

@ -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)
}

View file

@ -35,6 +35,7 @@ const (
TxPoolApiName = "txpool"
PersonalApiName = "personal"
Web3ApiName = "web3"
ChequebookApiName = "chequebook"
JsonRpcVersion = "2.0"
)
@ -42,7 +43,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,
}, ",")
)

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

50
rpc/api/chequebook_js.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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]
}),
]
});
`

View file

@ -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:

View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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))

58
swarm/api/dns.go Normal file
View file

@ -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
}

258
swarm/api/filesystem.go Normal file
View file

@ -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
}

View file

@ -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)
})
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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 {

View file

@ -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]

View file

@ -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)
}

56
swarm/api/storage.go Normal file
View file

@ -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)
}

33
swarm/api/storage_test.go Normal file
View file

@ -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)
})
}

View file

@ -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
}