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" TxPoolApiName = "txpool"
PersonalApiName = "personal" PersonalApiName = "personal"
Web3ApiName = "web3" Web3ApiName = "web3"
ChequebookApiName = "chequebook"
JsonRpcVersion = "2.0" JsonRpcVersion = "2.0"
) )
@ -42,7 +43,7 @@ const (
var ( var (
// All API's // All API's
AllApis = strings.Join([]string{ AllApis = strings.Join([]string{
AdminApiName, BzzApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, AdminApiName, BzzApiName, ChequebookApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName,
ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, 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", "verbosity",
}, },
"bzz": []string{ "bzz": []string{
"version",
"info", "info",
"issue",
"cash",
"deposit",
"register", "register",
"resolve", "resolve",
"download", "download",
@ -76,6 +74,13 @@ var (
"put", "put",
"modify", "modify",
}, },
"chequebook": []string{
"version",
"info",
"issue",
"cash",
"deposit",
},
"db": []string{ "db": []string{
"getString", "getString",
"putString", "putString",
@ -129,7 +134,7 @@ var (
"sign", "sign",
"syncing", "syncing",
}, },
"miner": []string{ "miner ": []string{
"hashrate", "hashrate",
"makeDAG", "makeDAG",
"setEtherbase", "setEtherbase",
@ -195,7 +200,7 @@ func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *no
case shared.AdminApiName: case shared.AdminApiName:
apis[i] = NewAdminApi(xeth, stack, codec) apis[i] = NewAdminApi(xeth, stack, codec)
case shared.BzzApiName: case shared.BzzApiName:
apis[i] = NewBzzApi(stack, codec) case shared.ChequebookApiName:
case shared.DebugApiName: case shared.DebugApiName:
apis[i] = NewDebugApi(xeth, eth, codec) apis[i] = NewDebugApi(xeth, eth, codec)
case shared.DbApiName: case shared.DbApiName:
@ -229,6 +234,8 @@ func Javascript(name string) string {
return Admin_JS return Admin_JS
case shared.BzzApiName: case shared.BzzApiName:
return Bzz_JS return Bzz_JS
case shared.ChequebookApiName:
return Chequebook_JS
case shared.DebugApiName: case shared.DebugApiName:
return Debug_JS return Debug_JS
case shared.DbApiName: case shared.DbApiName:

View file

@ -1,24 +1,16 @@
package api package api
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"math/big"
"net/http"
"os"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/common" "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"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/storage"
) )
var ( var (
@ -27,68 +19,86 @@ var (
domainAndVersion = regexp.MustCompile("[@:;,]+") domainAndVersion = regexp.MustCompile("[@:;,]+")
) )
type Resolver interface {
Resolve(string) (storage.Key, error)
}
/* /*
Api implements webserver/file system related content storage and retrieval Api implements webserver/file system related content storage and retrieval
on top of the dpa on top of the dpa
it is the public interface of the dpa which is included in the ethereum stack it is the public interface of the dpa which is included in the ethereum stack
*/ */
type Api struct { type Api struct {
dpa *storage.DPA dpa *storage.DPA
registrar registrar.VersionedRegistrar dns Resolver
conf *Config
} }
//the api constructor initialises //the api constructor initialises
func NewApi(dpa *storage.DPA, registrar registrar.VersionedRegistrar, conf *Config) (self *Api) { func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) {
return &Api{dpa, registrar, conf} self = &Api{
} dpa: dpa,
dns: dns,
// 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
} }
return return
} }
// Put provides singleton manifest creation and optional name registration // DPA reader API
// on top of dpa store 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) { func (self *Api) Put(content, contentType string) (string, error) {
sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content))) sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content)))
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
@ -106,6 +116,34 @@ func (self *Api) Put(content, contentType string) (string, error) {
return key.String(), nil 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) { func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) {
root := common.Hex2Bytes(rootHash) root := common.Hex2Bytes(rootHash)
trie, err := loadManifest(self.dpa, root) 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 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 package api
import ( import (
"bytes" // "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"runtime"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
//TODO: add tests for resolver/registrar func testApi(t *testing.T, f func(*Api)) {
// 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) {
datadir, err := ioutil.TempDir("", "bzz-test") datadir, err := ioutil.TempDir("", "bzz-test")
if err != nil { if err != nil {
return nil, err t.Fatalf("unable to create temp dir: %v", err)
} }
os.RemoveAll(datadir) os.RemoveAll(datadir)
defer os.RemoveAll(datadir)
dpa, err := storage.NewLocalDPA(datadir) dpa, err := storage.NewLocalDPA(datadir)
if err != nil { if err != nil {
return return
} }
prvkey, _ := crypto.GenerateKey() api := NewApi(dpa, nil)
dpa.Start()
f(api)
dpa.Stop()
}
config, err := NewConfig(datadir, common.Address{}, prvkey) type testResponse struct {
if err != nil { reader storage.SectionReader
return *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) if resp.Status != exp.Status {
api.dpa.Start() 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) { func TestApiPut(t *testing.T) {
api, err := testApi() testApi(t, func(api *Api) {
if err != nil { content := "hello"
t.Errorf("unexpected error: %v", err) exp := expResponse(content, "text/plain", 0)
return // exp := expResponse([]byte(content), "text/plain", 0)
} bzzhash, err := api.Put(content, exp.MimeType)
defer api.dpa.Stop() if err != nil {
expContent := "hello" t.Fatalf("unexpected error: %v", err)
expMimeType := "text/plain" }
expStatus := 0 resp := testGet(t, api, bzzhash)
expSize := len(expContent) checkResponse(t, resp, exp)
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)
} }

View file

@ -91,6 +91,7 @@ func TestConfigWriteRead(t *testing.T) {
t.Fatalf("default config file cannot be read: %v", err) t.Fatalf("default config file cannot be read: %v", err)
} }
exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1) exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1)
exp = strings.Replace(exp, "\\", "\\\\", -1)
if string(data) != exp { if string(data) != exp {
t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data)) 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 ( import (
"io/ioutil" "io/ioutil"
@ -22,7 +22,7 @@ func TestRoundTripper(t *testing.T) {
}) })
go http.ListenAndServe(":8600", serveMux) go http.ListenAndServe(":8600", serveMux)
rt := &RoundTripper{"8600"} rt := &RoundTripper{Port: "8600"}
client := httpclient.New("/") client := httpclient.New("/")
client.RegisterProtocol("bzz", rt) client.RegisterProtocol("bzz", rt)

View file

@ -1,7 +1,7 @@
/* /*
A simple http server interface to Swarm A simple http server interface to Swarm
*/ */
package api package http
import ( import (
"bytes" "bytes"
@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/api"
) )
const ( const (
@ -41,7 +42,7 @@ type sequentialReader struct {
// https://github.com/atom/electron/blob/master/docs/api/protocol.md // https://github.com/atom/electron/blob/master/docs/api/protocol.md
// starts up http server // starts up http server
func StartHttpServer(api *Api, port string) { func StartHttpServer(api *api.Api, port string) {
serveMux := http.NewServeMux() serveMux := http.NewServeMux()
serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handler(w, r, api) 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) 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 requestURL := r.URL
// This is wrong // This is wrong
// if requestURL.Host == "" { // if requestURL.Host == "" {
@ -78,7 +79,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
switch { switch {
case r.Method == "POST" || r.Method == "PUT": 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, reader: r.Body,
ahead: make(map[int64]chan bool), ahead: make(map[int64]chan bool),
}, 0, r.ContentLength), nil) }, 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) http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest)
return return
} else { } else {
path = regularSlashes(path) path = api.RegularSlashes(path)
mime := r.Header.Get("Content-Type") mime := r.Header.Get("Content-Type")
// TODO proper root hash separation // TODO proper root hash separation
glog.V(logger.Debug).Infof("[BZZ] Modify '%s' to store %v as '%s'.", path, key.Log(), mime) 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 { if err == nil {
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
w.Header().Set("Content-Type", "text/plain") 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) http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest)
return return
} else { } else {
path = regularSlashes(path) path = api.RegularSlashes(path)
glog.V(logger.Debug).Infof("[BZZ] Delete '%s'.", 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 { if err == nil {
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
w.Header().Set("Content-Type", "text/plain") 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, "") path = trailingSlashes.ReplaceAllString(path, "")
if raw { if raw {
// resolving host // resolving host
key, err := api.Resolve(path) key, err := a.Resolve(path)
if err != nil { if err != nil {
glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err) glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
@ -146,7 +147,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
} }
// retrieving content // retrieving content
reader := api.dpa.Retrieve(key) reader := a.Retrieve(key)
glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size()) glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size())
// setting mime type // 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) glog.V(logger.Debug).Infof("[BZZ] Swarm: Structured GET request '%s' received.", uri)
// call to api.getPath on uri reader, mimeType, status, err := a.Get(path)
reader, mimeType, status, err := api.getPath(path)
if err != nil { if err != nil {
if _, ok := err.(errResolve); ok { if _, ok := err.(api.ErrResolve); ok {
glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err) glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
status = http.StatusBadRequest status = http.StatusBadRequest
} else { } else {

View file

@ -290,7 +290,7 @@ func (self *manifestTrie) findPrefixOf(path string) (entry *manifestTrieEntry, p
// file system manifest always contains regularized paths // file system manifest always contains regularized paths
// no leading or trailing slashes, only single slashes inside // 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++ { for i := 0; i < len(path); i++ {
if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) {
res = res + path[i: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) { func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) {
path := regularSlashes(spath) path := RegularSlashes(spath)
var pos int var pos int
entry, pos = self.findPrefixOf(path) entry, pos = self.findPrefixOf(path)
return entry, path[:pos] 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/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
rpc "github.com/ethereum/go-ethereum/rpc/v2"
"github.com/ethereum/go-ethereum/swarm/api" "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/network"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
const (
Namespace = "bzz"
Version = "0.1" // versioning reflect POC and release versions
)
// the swarm stack // the swarm stack
type Swarm struct { type Swarm struct {
config *api.Config // swarm configuration config *api.Config // swarm configuration
api *api.Api // high level api layer (fs/manifest) 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 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 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 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 // setup cloud storage internal access layer
self.storage = storage.NewNetStore(hash, lstore, cloud, config.StoreParams) 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) self.depo = network.NewDepo(hash, lstore, self.storage)
glog.V(logger.Debug).Infof("[BZZ] -> REmote Access to CHunks") 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") glog.V(logger.Debug).Infof("[BZZ] -> Local Access to Swarm")
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams) 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 // set up high level api
backend := api.NewEthApi(ethereum) backend := api.NewEthApi(ethereum)
backend.UpdateState() 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 // 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 // set chequebook
if swapEnabled { if swapEnabled {
@ -143,7 +154,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
// start swarm http proxy server // start swarm http proxy server
if self.config.Port != "" { 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) 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, "bzz": self.config.Port,
} }
for scheme, port := range schemes { 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) 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} return []p2p.Protocol{proto}
} }
func (self *Swarm) Api() *api.Api { // implements node.Service
return self.api // 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 // Backend interface implemented by eth or JSON-IPC client
@ -223,9 +247,19 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {
} }
self = &Swarm{ self = &Swarm{
api: api.NewApi(dpa, nil, config), api: api.NewApi(dpa, nil),
config: config, config: config,
} }
return return
} }
// serialisable info about swarm
type Info struct {
*api.Config
*chequebook.Params
}
func (self *Info) Info() *Info {
return self
}