mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
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:
parent
154fb69f7e
commit
e85b9b7397
21 changed files with 954 additions and 1312 deletions
|
|
@ -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
29
common/chequebook/api.go
Normal 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)
|
||||||
|
}
|
||||||
278
rpc/api/bzz.go
278
rpc/api/bzz.go
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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
50
rpc/api/chequebook_js.go
Normal 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]
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import "strings"
|
||||||
const (
|
const (
|
||||||
AdminApiName = "admin"
|
AdminApiName = "admin"
|
||||||
BzzApiName = "bzz"
|
BzzApiName = "bzz"
|
||||||
|
ChequebookApiName = "chequebook"
|
||||||
EthApiName = "eth"
|
EthApiName = "eth"
|
||||||
DbApiName = "db"
|
DbApiName = "db"
|
||||||
DebugApiName = "debug"
|
DebugApiName = "debug"
|
||||||
|
|
@ -38,7 +39,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,
|
||||||
}, ",")
|
}, ",")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
468
swarm/api/api.go
468
swarm/api/api.go
|
|
@ -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,6 +19,10 @@ 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
|
||||||
|
|
@ -34,61 +30,75 @@ 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,
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get uses iterative manifest retrieval and prefix matching
|
// DPA reader API
|
||||||
// to resolve path to content using dpa retrieve
|
func (self *Api) Retrieve(key storage.Key) storage.SectionReader {
|
||||||
func (self *Api) Get(bzzpath string) (content []byte, mimeType string, status int, size int, err error) {
|
return self.dpa.Retrieve(key)
|
||||||
var reader storage.SectionReader
|
}
|
||||||
reader, mimeType, status, err = self.getPath("/" + bzzpath)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return
|
err = ErrResolve(err)
|
||||||
}
|
glog.V(logger.Debug).Infof("[BZZ] DNS error : %v", err)
|
||||||
content = make([]byte, reader.Size())
|
|
||||||
size, err = reader.Read(content)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
}
|
||||||
|
glog.V(logger.Debug).Infof("[BZZ] host lookup: %v -> %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put provides singleton manifest creation and optional name registration
|
func parse(uri string) (hostPort, path string) {
|
||||||
// on top of dpa store
|
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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
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 {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer api.dpa.Stop()
|
resp := testGet(t, api, bzzhash)
|
||||||
expContent := "hello"
|
checkResponse(t, resp, exp)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
58
swarm/api/dns.go
Normal 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
258
swarm/api/filesystem.go
Normal 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
|
||||||
|
}
|
||||||
176
swarm/api/filesystem_test.go
Normal file
176
swarm/api/filesystem_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
49
swarm/api/http/roundtripper.go
Normal file
49
swarm/api/http/roundtripper.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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
56
swarm/api/storage.go
Normal 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
33
swarm/api/storage_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue