swarm: push tags integration - request flow
swarm/api: integrate tags to count chunks being split and stored swarm/api/http: integrate tags in middleware for HTTP `POST` calls and assert chunks being calculated and counted correctly swarm: remove deprecated and unused code, add swarm hash to DoneSplit signature, remove calls to the api client from the http package
This commit is contained in:
parent
3030893a21
commit
ad6c39012f
@ -23,6 +23,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
@ -47,7 +48,7 @@ func hashes(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
refs, err := fileStore.GetAllReferences(context.TODO(), f, false)
|
refs, err := fileStore.GetAllReferences(context.TODO(), f, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("%v\n", err)
|
utils.Fatalf("%v\n", err)
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
@ -77,7 +78,7 @@ func hash(ctx *cli.Context) {
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
stat, _ := f.Stat()
|
stat, _ := f.Stat()
|
||||||
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
|
addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("%v\n", err)
|
utils.Fatalf("%v\n", err)
|
||||||
|
@ -255,7 +255,7 @@ func getAllRefs(testData []byte) (storage.AddressCollection, error) {
|
|||||||
return nil, fmt.Errorf("unable to create temp dir: %v", err)
|
return nil, fmt.Errorf("unable to create temp dir: %v", err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(datadir)
|
defer os.RemoveAll(datadir)
|
||||||
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32))
|
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32), chunk.NewTags())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
@ -53,8 +54,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
|
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
|
||||||
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
|
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
|
||||||
apiPutCount = metrics.NewRegisteredCounter("api.put.count", nil)
|
|
||||||
apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil)
|
|
||||||
apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil)
|
apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil)
|
||||||
apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil)
|
apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil)
|
||||||
apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil)
|
apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil)
|
||||||
@ -188,15 +187,17 @@ type API struct {
|
|||||||
feed *feed.Handler
|
feed *feed.Handler
|
||||||
fileStore *storage.FileStore
|
fileStore *storage.FileStore
|
||||||
dns Resolver
|
dns Resolver
|
||||||
|
Tags *chunk.Tags
|
||||||
Decryptor func(context.Context, string) DecryptFunc
|
Decryptor func(context.Context, string) DecryptFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPI the api constructor initialises a new API instance.
|
// NewAPI the api constructor initialises a new API instance.
|
||||||
func NewAPI(fileStore *storage.FileStore, dns Resolver, feedHandler *feed.Handler, pk *ecdsa.PrivateKey) (self *API) {
|
func NewAPI(fileStore *storage.FileStore, dns Resolver, feedHandler *feed.Handler, pk *ecdsa.PrivateKey, tags *chunk.Tags) (self *API) {
|
||||||
self = &API{
|
self = &API{
|
||||||
fileStore: fileStore,
|
fileStore: fileStore,
|
||||||
dns: dns,
|
dns: dns,
|
||||||
feed: feedHandler,
|
feed: feedHandler,
|
||||||
|
Tags: tags,
|
||||||
Decryptor: func(ctx context.Context, credentials string) DecryptFunc {
|
Decryptor: func(ctx context.Context, credentials string) DecryptFunc {
|
||||||
return self.doDecrypt(ctx, credentials, pk)
|
return self.doDecrypt(ctx, credentials, pk)
|
||||||
},
|
},
|
||||||
@ -297,31 +298,6 @@ func (a *API) ResolveURI(ctx context.Context, uri *URI, credentials string) (sto
|
|||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put provides singleton manifest creation on top of FileStore store
|
|
||||||
func (a *API) Put(ctx context.Context, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) {
|
|
||||||
apiPutCount.Inc(1)
|
|
||||||
r := strings.NewReader(content)
|
|
||||||
key, waitContent, err := a.fileStore.Store(ctx, r, int64(len(content)), toEncrypt)
|
|
||||||
if err != nil {
|
|
||||||
apiPutFail.Inc(1)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
|
|
||||||
r = strings.NewReader(manifest)
|
|
||||||
key, waitManifest, err := a.fileStore.Store(ctx, r, int64(len(manifest)), toEncrypt)
|
|
||||||
if err != nil {
|
|
||||||
apiPutFail.Inc(1)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return key, func(ctx context.Context) error {
|
|
||||||
err := waitContent(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return waitManifest(ctx)
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get uses iterative manifest retrieval and prefix matching
|
// Get uses iterative manifest retrieval and prefix matching
|
||||||
// to resolve basePath to content using FileStore retrieve
|
// to resolve basePath to content using FileStore retrieve
|
||||||
// it returns a section reader, mimeType, status, the key of the actual content and an error
|
// it returns a section reader, mimeType, status, the key of the actual content and an error
|
||||||
|
@ -19,6 +19,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
crand "crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -26,13 +27,16 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/sctx"
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -41,19 +45,21 @@ func init() {
|
|||||||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAPI(t *testing.T, f func(*API, bool)) {
|
func testAPI(t *testing.T, f func(*API, *chunk.Tags, bool)) {
|
||||||
datadir, err := ioutil.TempDir("", "bzz-test")
|
for _, v := range []bool{true, false} {
|
||||||
if err != nil {
|
datadir, err := ioutil.TempDir("", "bzz-test")
|
||||||
t.Fatalf("unable to create temp dir: %v", err)
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(datadir)
|
||||||
|
tags := chunk.NewTags()
|
||||||
|
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32), tags)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api := NewAPI(fileStore, nil, nil, nil, tags)
|
||||||
|
f(api, tags, v)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(datadir)
|
|
||||||
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
api := NewAPI(fileStore, nil, nil, nil)
|
|
||||||
f(api, false)
|
|
||||||
f(api, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type testResponse struct {
|
type testResponse struct {
|
||||||
@ -61,6 +67,13 @@ type testResponse struct {
|
|||||||
*Response
|
*Response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
MimeType string
|
||||||
|
Status int
|
||||||
|
Size int64
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
func checkResponse(t *testing.T, resp *testResponse, exp *Response) {
|
func checkResponse(t *testing.T, resp *testResponse, exp *Response) {
|
||||||
|
|
||||||
if resp.MimeType != exp.MimeType {
|
if resp.MimeType != exp.MimeType {
|
||||||
@ -111,15 +124,14 @@ func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse {
|
|||||||
}
|
}
|
||||||
reader.Seek(0, 0)
|
reader.Seek(0, 0)
|
||||||
return &testResponse{reader, &Response{mimeType, status, size, string(s)}}
|
return &testResponse{reader, &Response{mimeType, status, size, string(s)}}
|
||||||
// return &testResponse{reader, &Response{mimeType, status, reader.Size(), nil}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApiPut(t *testing.T) {
|
func TestApiPut(t *testing.T) {
|
||||||
testAPI(t, func(api *API, toEncrypt bool) {
|
testAPI(t, func(api *API, tags *chunk.Tags, toEncrypt bool) {
|
||||||
content := "hello"
|
content := "hello"
|
||||||
exp := expResponse(content, "text/plain", 0)
|
exp := expResponse(content, "text/plain", 0)
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
addr, wait, err := api.Put(ctx, content, exp.MimeType, toEncrypt)
|
addr, wait, err := putString(ctx, api, content, exp.MimeType, toEncrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@ -129,6 +141,40 @@ func TestApiPut(t *testing.T) {
|
|||||||
}
|
}
|
||||||
resp := testGet(t, api, addr.Hex(), "")
|
resp := testGet(t, api, addr.Hex(), "")
|
||||||
checkResponse(t, resp, exp)
|
checkResponse(t, resp, exp)
|
||||||
|
tag := tags.All()[0]
|
||||||
|
testutil.CheckTag(t, tag, 2, 2, 0, 2) //1 chunk data, 1 chunk manifest
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestApiTagLarge tests that the the number of chunks counted is larger for a larger input
|
||||||
|
func TestApiTagLarge(t *testing.T) {
|
||||||
|
const contentLength = 4096 * 4095
|
||||||
|
testAPI(t, func(api *API, tags *chunk.Tags, toEncrypt bool) {
|
||||||
|
randomContentReader := io.LimitReader(crand.Reader, int64(contentLength))
|
||||||
|
tag, err := api.Tags.New("unnamed-tag", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ctx := sctx.SetTag(context.Background(), tag.Uid)
|
||||||
|
key, waitContent, err := api.Store(ctx, randomContentReader, int64(contentLength), toEncrypt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = waitContent(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tag.DoneSplit(key)
|
||||||
|
|
||||||
|
if toEncrypt {
|
||||||
|
tag := tags.All()[0]
|
||||||
|
expect := int64(4095 + 64 + 1)
|
||||||
|
testutil.CheckTag(t, tag, expect, expect, 0, expect)
|
||||||
|
} else {
|
||||||
|
tag := tags.All()[0]
|
||||||
|
expect := int64(4095 + 32 + 1)
|
||||||
|
testutil.CheckTag(t, tag, expect, expect, 0, expect)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +437,7 @@ func TestDecryptOriginForbidden(t *testing.T) {
|
|||||||
Access: &AccessEntry{Type: AccessTypePass},
|
Access: &AccessEntry{Type: AccessTypePass},
|
||||||
}
|
}
|
||||||
|
|
||||||
api := NewAPI(nil, nil, nil, nil)
|
api := NewAPI(nil, nil, nil, nil, chunk.NewTags())
|
||||||
|
|
||||||
f := api.Decryptor(ctx, "")
|
f := api.Decryptor(ctx, "")
|
||||||
err := f(me)
|
err := f(me)
|
||||||
@ -425,7 +471,7 @@ func TestDecryptOrigin(t *testing.T) {
|
|||||||
Access: &AccessEntry{Type: AccessTypePass},
|
Access: &AccessEntry{Type: AccessTypePass},
|
||||||
}
|
}
|
||||||
|
|
||||||
api := NewAPI(nil, nil, nil, nil)
|
api := NewAPI(nil, nil, nil, nil, chunk.NewTags())
|
||||||
|
|
||||||
f := api.Decryptor(ctx, "")
|
f := api.Decryptor(ctx, "")
|
||||||
err := f(me)
|
err := f(me)
|
||||||
@ -500,3 +546,31 @@ func TestDetectContentType(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// putString provides singleton manifest creation on top of api.API
|
||||||
|
func putString(ctx context.Context, a *API, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) {
|
||||||
|
r := strings.NewReader(content)
|
||||||
|
tag, err := a.Tags.New("unnamed-tag", 0)
|
||||||
|
|
||||||
|
log.Trace("created new tag", "uid", tag.Uid)
|
||||||
|
|
||||||
|
cCtx := sctx.SetTag(ctx, tag.Uid)
|
||||||
|
key, waitContent, err := a.Store(cCtx, r, int64(len(content)), toEncrypt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
|
||||||
|
r = strings.NewReader(manifest)
|
||||||
|
key, waitManifest, err := a.Store(cCtx, r, int64(len(manifest)), toEncrypt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tag.DoneSplit(key)
|
||||||
|
return key, func(ctx context.Context) error {
|
||||||
|
err := waitContent(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return waitManifest(ctx)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -40,6 +40,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
@ -75,6 +76,8 @@ func (c *Client) UploadRaw(r io.Reader, size int64, toEncrypt bool) (string, err
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
req.ContentLength = size
|
req.ContentLength = size
|
||||||
|
req.Header.Set(swarmhttp.SwarmTagHeaderName, fmt.Sprintf("raw_upload_%d", time.Now().Unix()))
|
||||||
|
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -111,6 +114,7 @@ func (c *Client) DownloadRaw(hash string) (io.ReadCloser, bool, error) {
|
|||||||
type File struct {
|
type File struct {
|
||||||
io.ReadCloser
|
io.ReadCloser
|
||||||
api.ManifestEntry
|
api.ManifestEntry
|
||||||
|
Tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a local file which can then be passed to client.Upload to upload
|
// Open opens a local file which can then be passed to client.Upload to upload
|
||||||
@ -139,6 +143,7 @@ func Open(path string) (*File, error) {
|
|||||||
Size: stat.Size(),
|
Size: stat.Size(),
|
||||||
ModTime: stat.ModTime(),
|
ModTime: stat.ModTime(),
|
||||||
},
|
},
|
||||||
|
Tag: filepath.Base(path),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,6 +427,7 @@ func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, erro
|
|||||||
// Uploader uploads files to swarm using a provided UploadFn
|
// Uploader uploads files to swarm using a provided UploadFn
|
||||||
type Uploader interface {
|
type Uploader interface {
|
||||||
Upload(UploadFn) error
|
Upload(UploadFn) error
|
||||||
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploaderFunc func(UploadFn) error
|
type UploaderFunc func(UploadFn) error
|
||||||
@ -430,12 +436,23 @@ func (u UploaderFunc) Upload(upload UploadFn) error {
|
|||||||
return u(upload)
|
return u(upload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u UploaderFunc) Tag() string {
|
||||||
|
return fmt.Sprintf("multipart_upload_%d", time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectoryUploader implements Uploader
|
||||||
|
var _ Uploader = &DirectoryUploader{}
|
||||||
|
|
||||||
// DirectoryUploader uploads all files in a directory, optionally uploading
|
// DirectoryUploader uploads all files in a directory, optionally uploading
|
||||||
// a file to the default path
|
// a file to the default path
|
||||||
type DirectoryUploader struct {
|
type DirectoryUploader struct {
|
||||||
Dir string
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DirectoryUploader) Tag() string {
|
||||||
|
return filepath.Base(d.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
// Upload performs the upload of the directory and default path
|
// Upload performs the upload of the directory and default path
|
||||||
func (d *DirectoryUploader) Upload(upload UploadFn) error {
|
func (d *DirectoryUploader) Upload(upload UploadFn) error {
|
||||||
return filepath.Walk(d.Dir, func(path string, f os.FileInfo, err error) error {
|
return filepath.Walk(d.Dir, func(path string, f os.FileInfo, err error) error {
|
||||||
@ -458,11 +475,17 @@ func (d *DirectoryUploader) Upload(upload UploadFn) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ Uploader = &FileUploader{}
|
||||||
|
|
||||||
// FileUploader uploads a single file
|
// FileUploader uploads a single file
|
||||||
type FileUploader struct {
|
type FileUploader struct {
|
||||||
File *File
|
File *File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FileUploader) Tag() string {
|
||||||
|
return f.File.Tag
|
||||||
|
}
|
||||||
|
|
||||||
// Upload performs the upload of the file
|
// Upload performs the upload of the file
|
||||||
func (f *FileUploader) Upload(upload UploadFn) error {
|
func (f *FileUploader) Upload(upload UploadFn) error {
|
||||||
return upload(f.File)
|
return upload(f.File)
|
||||||
@ -509,6 +532,14 @@ func (c *Client) TarUpload(hash string, uploader Uploader, defaultPath string, t
|
|||||||
req.URL.RawQuery = q.Encode()
|
req.URL.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tag := uploader.Tag()
|
||||||
|
if tag == "" {
|
||||||
|
tag = "unnamed_tag_" + fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
}
|
||||||
|
log.Trace("setting upload tag", "tag", tag)
|
||||||
|
|
||||||
|
req.Header.Set(swarmhttp.SwarmTagHeaderName, tag)
|
||||||
|
|
||||||
// use 'Expect: 100-continue' so we don't send the request body if
|
// use 'Expect: 100-continue' so we don't send the request body if
|
||||||
// the server refuses the request
|
// the server refuses the request
|
||||||
req.Header.Set("Expect", "100-continue")
|
req.Header.Set("Expect", "100-continue")
|
||||||
@ -574,6 +605,7 @@ func (c *Client) MultipartUpload(hash string, uploader Uploader) (string, error)
|
|||||||
|
|
||||||
mw := multipart.NewWriter(reqW)
|
mw := multipart.NewWriter(reqW)
|
||||||
req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", mw.Boundary()))
|
req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", mw.Boundary()))
|
||||||
|
req.Header.Set(swarmhttp.SwarmTagHeaderName, fmt.Sprintf("multipart_upload_%d", time.Now().Unix()))
|
||||||
|
|
||||||
// define an UploadFn which adds files to the multipart form
|
// define an UploadFn which adds files to the multipart form
|
||||||
uploadFn := func(file *File) error {
|
uploadFn := func(file *File) error {
|
||||||
|
@ -25,16 +25,14 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serverFunc(api *api.API) swarmhttp.TestServer {
|
func serverFunc(api *api.API) swarmhttp.TestServer {
|
||||||
@ -68,6 +66,10 @@ func testClientUploadDownloadRaw(toEncrypt bool, t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the tag was created successfully
|
||||||
|
tag := srv.Tags.All()[0]
|
||||||
|
testutil.CheckTag(t, tag, 1, 1, 0, 1)
|
||||||
|
|
||||||
// check we can download the same data
|
// check we can download the same data
|
||||||
res, isEncrypted, err := client.DownloadRaw(hash)
|
res, isEncrypted, err := client.DownloadRaw(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -209,6 +211,10 @@ func TestClientUploadDownloadDirectory(t *testing.T) {
|
|||||||
t.Fatalf("error uploading directory: %s", err)
|
t.Fatalf("error uploading directory: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the tag was created successfully
|
||||||
|
tag := srv.Tags.All()[0]
|
||||||
|
testutil.CheckTag(t, tag, 9, 9, 0, 9)
|
||||||
|
|
||||||
// check we can download the individual files
|
// check we can download the individual files
|
||||||
checkDownloadFile := func(path string, expected []byte) {
|
checkDownloadFile := func(path string, expected []byte) {
|
||||||
file, err := client.Download(hash, path)
|
file, err := client.Download(hash, path)
|
||||||
@ -323,6 +329,7 @@ func TestClientMultipartUpload(t *testing.T) {
|
|||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
// define an uploader which uploads testDirFiles with some data
|
// define an uploader which uploads testDirFiles with some data
|
||||||
|
// note: this test should result in SEEN chunks. assert accordingly
|
||||||
data := []byte("some-data")
|
data := []byte("some-data")
|
||||||
uploader := UploaderFunc(func(upload UploadFn) error {
|
uploader := UploaderFunc(func(upload UploadFn) error {
|
||||||
for _, name := range testDirFiles {
|
for _, name := range testDirFiles {
|
||||||
@ -348,6 +355,10 @@ func TestClientMultipartUpload(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the tag was created successfully
|
||||||
|
tag := srv.Tags.All()[0]
|
||||||
|
testutil.CheckTag(t, tag, 9, 9, 7, 9)
|
||||||
|
|
||||||
// check we can download the individual files
|
// check we can download the individual files
|
||||||
checkDownloadFile := func(path string) {
|
checkDownloadFile := func(path string) {
|
||||||
file, err := client.Download(hash, path)
|
file, err := client.Download(hash, path)
|
||||||
|
@ -25,13 +25,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test")
|
var testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test")
|
||||||
|
|
||||||
func testFileSystem(t *testing.T, f func(*FileSystem, bool)) {
|
func testFileSystem(t *testing.T, f func(*FileSystem, bool)) {
|
||||||
testAPI(t, func(api *API, toEncrypt bool) {
|
testAPI(t, func(api *API, _ *chunk.Tags, toEncrypt bool) {
|
||||||
f(NewFileSystem(api), toEncrypt)
|
f(NewFileSystem(api), toEncrypt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/sctx"
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||||
@ -86,6 +87,54 @@ func InitLoggingResponseWriter(h http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitUploadTag creates a new tag for an upload to the local HTTP proxy
|
||||||
|
// if a tag is not named using the SwarmTagHeaderName, a fallback name will be used
|
||||||
|
// when the Content-Length header is set, an ETA on chunking will be available since the
|
||||||
|
// number of chunks to be split is known in advance (not including enclosing manifest chunks)
|
||||||
|
// the tag can later be accessed using the appropriate identifier in the request context
|
||||||
|
func InitUploadTag(h http.Handler, tags *chunk.Tags) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
tagName string
|
||||||
|
err error
|
||||||
|
estimatedTotal int64 = 0
|
||||||
|
contentType = r.Header.Get("Content-Type")
|
||||||
|
headerTag = r.Header.Get(SwarmTagHeaderName)
|
||||||
|
)
|
||||||
|
if headerTag != "" {
|
||||||
|
tagName = headerTag
|
||||||
|
log.Trace("got tag name from http header", "tagName", tagName)
|
||||||
|
} else {
|
||||||
|
tagName = fmt.Sprintf("unnamed_tag_%d", time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(contentType, "multipart") && r.ContentLength > 0 {
|
||||||
|
log.Trace("calculating tag size", "contentType", contentType, "contentLength", r.ContentLength)
|
||||||
|
uri := GetURI(r.Context())
|
||||||
|
if uri != nil {
|
||||||
|
log.Debug("got uri from context")
|
||||||
|
if uri.Addr == "encrypt" {
|
||||||
|
estimatedTotal = calculateNumberOfChunks(r.ContentLength, true)
|
||||||
|
} else {
|
||||||
|
estimatedTotal = calculateNumberOfChunks(r.ContentLength, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("creating tag", "tagName", tagName, "estimatedTotal", estimatedTotal)
|
||||||
|
|
||||||
|
t, err := tags.New(tagName, estimatedTotal)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("error creating tag", "err", err, "tagName", tagName)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("setting tag id to context", "uid", t.Uid)
|
||||||
|
ctx := sctx.SetTag(r.Context(), t.Uid)
|
||||||
|
|
||||||
|
h.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func InstrumentOpenTracing(h http.Handler) http.Handler {
|
func InstrumentOpenTracing(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
uri := GetURI(r.Context())
|
uri := GetURI(r.Context())
|
||||||
|
@ -79,7 +79,7 @@ func respondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg s
|
|||||||
}
|
}
|
||||||
|
|
||||||
func respondError(w http.ResponseWriter, r *http.Request, msg string, code int) {
|
func respondError(w http.ResponseWriter, r *http.Request, msg string, code int) {
|
||||||
log.Info("respondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code)
|
log.Info("respondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code, "msg", msg)
|
||||||
respondTemplate(w, r, "error", msg, code)
|
respondTemplate(w, r, "error", msg, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -38,7 +39,9 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
@ -60,6 +63,8 @@ var (
|
|||||||
getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil)
|
getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const SwarmTagHeaderName = "x-swarm-tag"
|
||||||
|
|
||||||
type methodHandler map[string]http.Handler
|
type methodHandler map[string]http.Handler
|
||||||
|
|
||||||
func (m methodHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
func (m methodHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
@ -94,6 +99,12 @@ func NewServer(api *api.API, corsString string) *Server {
|
|||||||
InstrumentOpenTracing,
|
InstrumentOpenTracing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tagAdapter := Adapter(func(h http.Handler) http.Handler {
|
||||||
|
return InitUploadTag(h, api.Tags)
|
||||||
|
})
|
||||||
|
|
||||||
|
defaultPostMiddlewares := append(defaultMiddlewares, tagAdapter)
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.Handle("/bzz:/", methodHandler{
|
mux.Handle("/bzz:/", methodHandler{
|
||||||
"GET": Adapt(
|
"GET": Adapt(
|
||||||
@ -102,7 +113,7 @@ func NewServer(api *api.API, corsString string) *Server {
|
|||||||
),
|
),
|
||||||
"POST": Adapt(
|
"POST": Adapt(
|
||||||
http.HandlerFunc(server.HandlePostFiles),
|
http.HandlerFunc(server.HandlePostFiles),
|
||||||
defaultMiddlewares...,
|
defaultPostMiddlewares...,
|
||||||
),
|
),
|
||||||
"DELETE": Adapt(
|
"DELETE": Adapt(
|
||||||
http.HandlerFunc(server.HandleDelete),
|
http.HandlerFunc(server.HandleDelete),
|
||||||
@ -116,7 +127,7 @@ func NewServer(api *api.API, corsString string) *Server {
|
|||||||
),
|
),
|
||||||
"POST": Adapt(
|
"POST": Adapt(
|
||||||
http.HandlerFunc(server.HandlePostRaw),
|
http.HandlerFunc(server.HandlePostRaw),
|
||||||
defaultMiddlewares...,
|
defaultPostMiddlewares...,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
mux.Handle("/bzz-immutable:/", methodHandler{
|
mux.Handle("/bzz-immutable:/", methodHandler{
|
||||||
@ -230,6 +241,12 @@ func (s *Server) HandlePostRaw(w http.ResponseWriter, r *http.Request) {
|
|||||||
ruid := GetRUID(r.Context())
|
ruid := GetRUID(r.Context())
|
||||||
log.Debug("handle.post.raw", "ruid", ruid)
|
log.Debug("handle.post.raw", "ruid", ruid)
|
||||||
|
|
||||||
|
tagUid := sctx.GetTag(r.Context())
|
||||||
|
tag, err := s.api.Tags.Get(tagUid)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("handle post raw got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
postRawCount.Inc(1)
|
postRawCount.Inc(1)
|
||||||
|
|
||||||
toEncrypt := false
|
toEncrypt := false
|
||||||
@ -256,13 +273,16 @@ func (s *Server) HandlePostRaw(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, _, err := s.api.Store(r.Context(), r.Body, r.ContentLength, toEncrypt)
|
addr, wait, err := s.api.Store(r.Context(), r.Body, r.ContentLength, toEncrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
postRawFail.Inc(1)
|
postRawFail.Inc(1)
|
||||||
respondError(w, r, err.Error(), http.StatusInternalServerError)
|
respondError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait(r.Context())
|
||||||
|
tag.DoneSplit(addr)
|
||||||
|
|
||||||
log.Debug("stored content", "ruid", ruid, "key", addr)
|
log.Debug("stored content", "ruid", ruid, "key", addr)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
@ -311,7 +331,6 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
log.Debug("new manifest", "ruid", ruid, "key", addr)
|
log.Debug("new manifest", "ruid", ruid, "key", addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
newAddr, err := s.api.UpdateManifest(r.Context(), addr, func(mw *api.ManifestWriter) error {
|
newAddr, err := s.api.UpdateManifest(r.Context(), addr, func(mw *api.ManifestWriter) error {
|
||||||
switch contentType {
|
switch contentType {
|
||||||
case "application/x-tar":
|
case "application/x-tar":
|
||||||
@ -334,6 +353,15 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tagUid := sctx.GetTag(r.Context())
|
||||||
|
tag, err := s.api.Tags.Get(tagUid)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("got an error retrieving tag for DoneSplit", "tagUid", tagUid, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("done splitting, setting tag total", "SPLIT", tag.Get(chunk.StateSplit), "TOTAL", tag.Total())
|
||||||
|
tag.DoneSplit(newAddr)
|
||||||
|
|
||||||
log.Debug("stored content", "ruid", ruid, "key", newAddr)
|
log.Debug("stored content", "ruid", ruid, "key", newAddr)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
@ -342,7 +370,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleTarUpload(r *http.Request, mw *api.ManifestWriter) (storage.Address, error) {
|
func (s *Server) handleTarUpload(r *http.Request, mw *api.ManifestWriter) (storage.Address, error) {
|
||||||
log.Debug("handle.tar.upload", "ruid", GetRUID(r.Context()))
|
log.Debug("handle.tar.upload", "ruid", GetRUID(r.Context()), "tag", sctx.GetTag(r.Context()))
|
||||||
|
|
||||||
defaultPath := r.URL.Query().Get("defaultpath")
|
defaultPath := r.URL.Query().Get("defaultpath")
|
||||||
|
|
||||||
@ -837,6 +865,28 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize))
|
http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculateNumberOfChunks calculates the number of chunks in an arbitrary content length
|
||||||
|
func calculateNumberOfChunks(contentLength int64, isEncrypted bool) int64 {
|
||||||
|
if contentLength < 4096 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
branchingFactor := 128
|
||||||
|
if isEncrypted {
|
||||||
|
branchingFactor = 64
|
||||||
|
}
|
||||||
|
|
||||||
|
dataChunks := math.Ceil(float64(contentLength) / float64(4096))
|
||||||
|
totalChunks := dataChunks
|
||||||
|
intermediate := dataChunks / float64(branchingFactor)
|
||||||
|
|
||||||
|
for intermediate > 1 {
|
||||||
|
totalChunks += math.Ceil(intermediate)
|
||||||
|
intermediate = intermediate / float64(branchingFactor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int64(totalChunks) + 1
|
||||||
|
}
|
||||||
|
|
||||||
// The size of buffer used for bufio.Reader on LazyChunkReader passed to
|
// The size of buffer used for bufio.Reader on LazyChunkReader passed to
|
||||||
// http.ServeContent in HandleGetFile.
|
// http.ServeContent in HandleGetFile.
|
||||||
// Warning: This value influences the number of chunk requests and chunker join goroutines
|
// Warning: This value influences the number of chunk requests and chunker join goroutines
|
||||||
|
@ -44,7 +44,6 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
@ -755,6 +754,7 @@ func testBzzTar(encrypted bool, t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
req.Header.Add("Content-Type", "application/x-tar")
|
req.Header.Add("Content-Type", "application/x-tar")
|
||||||
|
req.Header.Add(SwarmTagHeaderName, "test-upload")
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp2, err := client.Do(req)
|
resp2, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -763,6 +763,11 @@ func testBzzTar(encrypted bool, t *testing.T) {
|
|||||||
if resp2.StatusCode != http.StatusOK {
|
if resp2.StatusCode != http.StatusOK {
|
||||||
t.Fatalf("err %s", resp2.Status)
|
t.Fatalf("err %s", resp2.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check that the tag was written correctly
|
||||||
|
tag := srv.Tags.All()[0]
|
||||||
|
testutil.CheckTag(t, tag, 4, 4, 0, 4)
|
||||||
|
|
||||||
swarmHash, err := ioutil.ReadAll(resp2.Body)
|
swarmHash, err := ioutil.ReadAll(resp2.Body)
|
||||||
resp2.Body.Close()
|
resp2.Body.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -834,6 +839,75 @@ func testBzzTar(encrypted bool, t *testing.T) {
|
|||||||
t.Fatalf("file %s did not pass content assertion", hdr.Name)
|
t.Fatalf("file %s did not pass content assertion", hdr.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// now check the tags endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBzzCorrectTagEstimate checks that the HTTP middleware sets the total number of chunks
|
||||||
|
// in the tag according to an estimate from the HTTP request Content-Length header divided
|
||||||
|
// by chunk size (4096). It is needed to be checked BEFORE chunking is done, therefore
|
||||||
|
// concurrency was introduced to slow down the HTTP request
|
||||||
|
func TestBzzCorrectTagEstimate(t *testing.T) {
|
||||||
|
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
for _, v := range []struct {
|
||||||
|
toEncrypt bool
|
||||||
|
expChunks int64
|
||||||
|
}{
|
||||||
|
{toEncrypt: false, expChunks: 248},
|
||||||
|
{toEncrypt: true, expChunks: 250},
|
||||||
|
} {
|
||||||
|
pr, pw := io.Pipe()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
addr := ""
|
||||||
|
if v.toEncrypt {
|
||||||
|
addr = "encrypt"
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", srv.URL+"/bzz:/"+addr, pr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
req.ContentLength = 1000000
|
||||||
|
req.Header.Add(SwarmTagHeaderName, "1000000")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(1 * time.Millisecond):
|
||||||
|
_, err := pw.Write([]byte{0})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
transport := http.DefaultTransport
|
||||||
|
_, err := transport.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
done := false
|
||||||
|
for !done {
|
||||||
|
switch len(srv.Tags.All()) {
|
||||||
|
case 0:
|
||||||
|
<-time.After(10 * time.Millisecond)
|
||||||
|
case 1:
|
||||||
|
tag := srv.Tags.All()[0]
|
||||||
|
testutil.CheckTag(t, tag, 0, 0, 0, v.expChunks)
|
||||||
|
srv.Tags.Delete(tag.Uid)
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBzzRootRedirect tests that getting the root path of a manifest without
|
// TestBzzRootRedirect tests that getting the root path of a manifest without
|
||||||
@ -851,19 +925,11 @@ func testBzzRootRedirect(toEncrypt bool, t *testing.T) {
|
|||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
// create a manifest with some data at the root path
|
// create a manifest with some data at the root path
|
||||||
client := swarm.NewClient(srv.URL)
|
|
||||||
data := []byte("data")
|
data := []byte("data")
|
||||||
file := &swarm.File{
|
headers := map[string]string{"Content-Type": "text/plain"}
|
||||||
ReadCloser: ioutil.NopCloser(bytes.NewReader(data)),
|
res, hash := httpDo("POST", srv.URL+"/bzz:/", bytes.NewReader(data), headers, false, t)
|
||||||
ManifestEntry: api.ManifestEntry{
|
if res.StatusCode != http.StatusOK {
|
||||||
Path: "",
|
t.Fatalf("unexpected status code from server %d want %d", res.StatusCode, http.StatusOK)
|
||||||
ContentType: "text/plain",
|
|
||||||
Size: int64(len(data)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
hash, err := client.Upload(file, "", toEncrypt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// define a CheckRedirect hook which ensures there is only a single
|
// define a CheckRedirect hook which ensures there is only a single
|
||||||
@ -1046,21 +1112,10 @@ func TestGet(t *testing.T) {
|
|||||||
func TestModify(t *testing.T) {
|
func TestModify(t *testing.T) {
|
||||||
srv := NewTestSwarmServer(t, serverFunc, nil)
|
srv := NewTestSwarmServer(t, serverFunc, nil)
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
headers := map[string]string{"Content-Type": "text/plain"}
|
||||||
swarmClient := swarm.NewClient(srv.URL)
|
res, hash := httpDo("POST", srv.URL+"/bzz:/", bytes.NewReader([]byte("data")), headers, false, t)
|
||||||
data := []byte("data")
|
if res.StatusCode != http.StatusOK {
|
||||||
file := &swarm.File{
|
t.Fatalf("unexpected status code from server %d want %d", res.StatusCode, http.StatusOK)
|
||||||
ReadCloser: ioutil.NopCloser(bytes.NewReader(data)),
|
|
||||||
ManifestEntry: api.ManifestEntry{
|
|
||||||
Path: "",
|
|
||||||
ContentType: "text/plain",
|
|
||||||
Size: int64(len(data)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
hash, err := swarmClient.Upload(file, "", false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range []struct {
|
for _, testCase := range []struct {
|
||||||
@ -1283,6 +1338,46 @@ func TestBzzGetFileWithResolver(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCalculateNumberOfChunks is a unit test for the chunk-number-according-to-content-length
|
||||||
|
// calculation
|
||||||
|
func TestCalculateNumberOfChunks(t *testing.T) {
|
||||||
|
|
||||||
|
//test cases:
|
||||||
|
for _, tc := range []struct{ len, chunks int64 }{
|
||||||
|
{len: 1000, chunks: 1},
|
||||||
|
{len: 5000, chunks: 3},
|
||||||
|
{len: 10000, chunks: 4},
|
||||||
|
{len: 100000, chunks: 26},
|
||||||
|
{len: 1000000, chunks: 248},
|
||||||
|
{len: 325839339210, chunks: 79550620 + 621490 + 4856 + 38 + 1},
|
||||||
|
} {
|
||||||
|
res := calculateNumberOfChunks(tc.len, false)
|
||||||
|
if res != tc.chunks {
|
||||||
|
t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCalculateNumberOfChunksEncrypted is a unit test for the chunk-number-according-to-content-length
|
||||||
|
// calculation with encryption (branching factor=64)
|
||||||
|
func TestCalculateNumberOfChunksEncrypted(t *testing.T) {
|
||||||
|
|
||||||
|
//test cases:
|
||||||
|
for _, tc := range []struct{ len, chunks int64 }{
|
||||||
|
{len: 1000, chunks: 1},
|
||||||
|
{len: 5000, chunks: 3},
|
||||||
|
{len: 10000, chunks: 4},
|
||||||
|
{len: 100000, chunks: 26},
|
||||||
|
{len: 1000000, chunks: 245 + 4 + 1},
|
||||||
|
{len: 325839339210, chunks: 79550620 + 1242979 + 19422 + 304 + 5 + 1},
|
||||||
|
} {
|
||||||
|
res := calculateNumberOfChunks(tc.len, true)
|
||||||
|
if res != tc.chunks {
|
||||||
|
t.Fatalf("expected result for %d bytes to be %d got %d", tc.len, tc.chunks, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// testResolver implements the Resolver interface and either returns the given
|
// testResolver implements the Resolver interface and either returns the given
|
||||||
// hash if it is set, or returns a "name not found" error
|
// hash if it is set, or returns a "name not found" error
|
||||||
type testResolveValidator struct {
|
type testResolveValidator struct {
|
||||||
@ -1308,6 +1403,7 @@ func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) {
|
|||||||
func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) {
|
func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) {
|
func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/localstore"
|
"github.com/ethereum/go-ethereum/swarm/storage/localstore"
|
||||||
@ -44,7 +45,9 @@ func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer, reso
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams())
|
tags := chunk.NewTags()
|
||||||
|
fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams(), tags)
|
||||||
|
|
||||||
// Swarm feeds test setup
|
// Swarm feeds test setup
|
||||||
feedsDir, err := ioutil.TempDir("", "swarm-feeds-test")
|
feedsDir, err := ioutil.TempDir("", "swarm-feeds-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -56,12 +59,13 @@ func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer, reso
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
swarmApi := api.NewAPI(fileStore, resolver, feeds.Handler, nil)
|
swarmApi := api.NewAPI(fileStore, resolver, feeds.Handler, nil, tags)
|
||||||
apiServer := httptest.NewServer(serverFunc(swarmApi))
|
apiServer := httptest.NewServer(serverFunc(swarmApi))
|
||||||
|
|
||||||
tss := &TestSwarmServer{
|
tss := &TestSwarmServer{
|
||||||
Server: apiServer,
|
Server: apiServer,
|
||||||
FileStore: fileStore,
|
FileStore: fileStore,
|
||||||
|
Tags: tags,
|
||||||
dir: swarmDir,
|
dir: swarmDir,
|
||||||
Hasher: storage.MakeHashFunc(storage.DefaultHash)(),
|
Hasher: storage.MakeHashFunc(storage.DefaultHash)(),
|
||||||
cleanup: func() {
|
cleanup: func() {
|
||||||
@ -81,6 +85,7 @@ type TestSwarmServer struct {
|
|||||||
*httptest.Server
|
*httptest.Server
|
||||||
Hasher storage.SwarmHash
|
Hasher storage.SwarmHash
|
||||||
FileStore *storage.FileStore
|
FileStore *storage.FileStore
|
||||||
|
Tags *chunk.Tags
|
||||||
dir string
|
dir string
|
||||||
cleanup func()
|
cleanup func()
|
||||||
CurrentTime uint64
|
CurrentTime uint64
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ func manifest(paths ...string) (manifestReader storage.LazySectionReader) {
|
|||||||
|
|
||||||
func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...string) *manifestTrie {
|
func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...string) *manifestTrie {
|
||||||
quitC := make(chan bool)
|
quitC := make(chan bool)
|
||||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
ref := make([]byte, fileStore.HashSize())
|
ref := make([]byte, fileStore.HashSize())
|
||||||
trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC, NOOPDecrypt)
|
trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC, NOOPDecrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,7 +100,7 @@ func TestGetEntry(t *testing.T) {
|
|||||||
func TestExactMatch(t *testing.T) {
|
func TestExactMatch(t *testing.T) {
|
||||||
quitC := make(chan bool)
|
quitC := make(chan bool)
|
||||||
mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map")
|
mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map")
|
||||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
ref := make([]byte, fileStore.HashSize())
|
ref := make([]byte, fileStore.HashSize())
|
||||||
trie, err := readManifest(mf, ref, fileStore, false, quitC, nil)
|
trie, err := readManifest(mf, ref, fileStore, false, quitC, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -132,7 +133,7 @@ func TestAddFileWithManifestPath(t *testing.T) {
|
|||||||
reader := &storage.LazyTestSectionReader{
|
reader := &storage.LazyTestSectionReader{
|
||||||
SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))),
|
SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))),
|
||||||
}
|
}
|
||||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
ref := make([]byte, fileStore.HashSize())
|
ref := make([]byte, fileStore.HashSize())
|
||||||
trie, err := readManifest(reader, ref, fileStore, false, nil, NOOPDecrypt)
|
trie, err := readManifest(reader, ref, fileStore, false, nil, NOOPDecrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
MimeType string
|
|
||||||
Status int
|
|
||||||
Size int64
|
|
||||||
// Content []byte
|
|
||||||
Content string
|
|
||||||
}
|
|
||||||
|
|
||||||
// implements a service
|
|
||||||
//
|
|
||||||
// DEPRECATED: Use the HTTP API instead
|
|
||||||
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
|
|
||||||
//
|
|
||||||
// DEPRECATED: Use the HTTP API instead
|
|
||||||
func (s *Storage) Put(ctx context.Context, content string, contentType string, toEncrypt bool) (storage.Address, func(context.Context) error, error) {
|
|
||||||
return s.api.Put(ctx, content, contentType, toEncrypt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// DEPRECATED: Use the HTTP API instead
|
|
||||||
func (s *Storage) Get(ctx context.Context, bzzpath string) (*Response, error) {
|
|
||||||
uri, err := Parse(path.Join("bzz:/", bzzpath))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
addr, err := s.api.Resolve(ctx, uri.Addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
reader, mimeType, status, _, err := s.api.Get(ctx, nil, addr, uri.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
quitC := make(chan bool)
|
|
||||||
expsize, err := reader.Size(ctx, quitC)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
body := make([]byte, expsize)
|
|
||||||
size, err := reader.Read(body)
|
|
||||||
if int64(size) == expsize {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return &Response{mimeType, status, expsize, string(body[:size])}, err
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testStorage(t *testing.T, f func(*Storage, bool)) {
|
|
||||||
testAPI(t, func(api *API, toEncrypt bool) {
|
|
||||||
f(NewStorage(api), toEncrypt)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoragePutGet(t *testing.T) {
|
|
||||||
testStorage(t, func(api *Storage, toEncrypt bool) {
|
|
||||||
content := "hello"
|
|
||||||
exp := expResponse(content, "text/plain", 0)
|
|
||||||
// exp := expResponse([]byte(content), "text/plain", 0)
|
|
||||||
ctx := context.TODO()
|
|
||||||
bzzkey, wait, err := api.Put(ctx, content, exp.MimeType, toEncrypt)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
err = wait(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
bzzhash := bzzkey.Hex()
|
|
||||||
// to check put against the API#Get
|
|
||||||
resp0 := testGet(t, api.api, bzzhash, "")
|
|
||||||
checkResponse(t, resp0, exp)
|
|
||||||
|
|
||||||
// check storage#Get
|
|
||||||
resp, err := api.Get(context.TODO(), bzzhash)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
checkResponse(t, &testResponse{nil, resp}, exp)
|
|
||||||
})
|
|
||||||
}
|
|
@ -34,11 +34,11 @@ var (
|
|||||||
type State = uint32
|
type State = uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SPLIT State = iota // chunk has been processed by filehasher/swarm safe call
|
StateSplit State = iota // chunk has been processed by filehasher/swarm safe call
|
||||||
STORED // chunk stored locally
|
StateStored // chunk stored locally
|
||||||
SEEN // chunk previously seen
|
StateSeen // chunk previously seen
|
||||||
SENT // chunk sent to neighbourhood
|
StateSent // chunk sent to neighbourhood
|
||||||
SYNCED // proof is received; chunk removed from sync db; chunk is available everywhere
|
StateSynced // proof is received; chunk removed from sync db; chunk is available everywhere
|
||||||
)
|
)
|
||||||
|
|
||||||
// Tag represents info on the status of new chunks
|
// Tag represents info on the status of new chunks
|
||||||
@ -46,18 +46,18 @@ type Tag struct {
|
|||||||
Uid uint32 // a unique identifier for this tag
|
Uid uint32 // a unique identifier for this tag
|
||||||
Name string // a name tag for this tag
|
Name string // a name tag for this tag
|
||||||
Address Address // the associated swarm hash for this tag
|
Address Address // the associated swarm hash for this tag
|
||||||
total uint32 // total chunks belonging to a tag
|
total int64 // total chunks belonging to a tag
|
||||||
split uint32 // number of chunks already processed by splitter for hashing
|
split int64 // number of chunks already processed by splitter for hashing
|
||||||
seen uint32 // number of chunks already seen
|
seen int64 // number of chunks already seen
|
||||||
stored uint32 // number of chunks already stored locally
|
stored int64 // number of chunks already stored locally
|
||||||
sent uint32 // number of chunks sent for push syncing
|
sent int64 // number of chunks sent for push syncing
|
||||||
synced uint32 // number of chunks synced with proof
|
synced int64 // number of chunks synced with proof
|
||||||
startedAt time.Time // tag started to calculate ETA
|
startedAt time.Time // tag started to calculate ETA
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new tag, stores it by the name and returns it
|
// New creates a new tag, stores it by the name and returns it
|
||||||
// it returns an error if the tag with this name already exists
|
// it returns an error if the tag with this name already exists
|
||||||
func NewTag(uid uint32, s string, total uint32) *Tag {
|
func NewTag(uid uint32, s string, total int64) *Tag {
|
||||||
t := &Tag{
|
t := &Tag{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
Name: s,
|
Name: s,
|
||||||
@ -69,65 +69,65 @@ func NewTag(uid uint32, s string, total uint32) *Tag {
|
|||||||
|
|
||||||
// Inc increments the count for a state
|
// Inc increments the count for a state
|
||||||
func (t *Tag) Inc(state State) {
|
func (t *Tag) Inc(state State) {
|
||||||
var v *uint32
|
var v *int64
|
||||||
switch state {
|
switch state {
|
||||||
case SPLIT:
|
case StateSplit:
|
||||||
v = &t.split
|
v = &t.split
|
||||||
case STORED:
|
case StateStored:
|
||||||
v = &t.stored
|
v = &t.stored
|
||||||
case SEEN:
|
case StateSeen:
|
||||||
v = &t.seen
|
v = &t.seen
|
||||||
case SENT:
|
case StateSent:
|
||||||
v = &t.sent
|
v = &t.sent
|
||||||
case SYNCED:
|
case StateSynced:
|
||||||
v = &t.synced
|
v = &t.synced
|
||||||
}
|
}
|
||||||
atomic.AddUint32(v, 1)
|
atomic.AddInt64(v, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the count for a state on a tag
|
// Get returns the count for a state on a tag
|
||||||
func (t *Tag) Get(state State) int {
|
func (t *Tag) Get(state State) int64 {
|
||||||
var v *uint32
|
var v *int64
|
||||||
switch state {
|
switch state {
|
||||||
case SPLIT:
|
case StateSplit:
|
||||||
v = &t.split
|
v = &t.split
|
||||||
case STORED:
|
case StateStored:
|
||||||
v = &t.stored
|
v = &t.stored
|
||||||
case SEEN:
|
case StateSeen:
|
||||||
v = &t.seen
|
v = &t.seen
|
||||||
case SENT:
|
case StateSent:
|
||||||
v = &t.sent
|
v = &t.sent
|
||||||
case SYNCED:
|
case StateSynced:
|
||||||
v = &t.synced
|
v = &t.synced
|
||||||
}
|
}
|
||||||
return int(atomic.LoadUint32(v))
|
return atomic.LoadInt64(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTotal returns the total count
|
// GetTotal returns the total count
|
||||||
func (t *Tag) Total() int {
|
func (t *Tag) Total() int64 {
|
||||||
return int(atomic.LoadUint32(&t.total))
|
return atomic.LoadInt64(&t.total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoneSplit sets total count to SPLIT count and sets the associated swarm hash for this tag
|
// DoneSplit sets total count to SPLIT count and sets the associated swarm hash for this tag
|
||||||
// is meant to be called when splitter finishes for input streams of unknown size
|
// is meant to be called when splitter finishes for input streams of unknown size
|
||||||
func (t *Tag) DoneSplit(address Address) int {
|
func (t *Tag) DoneSplit(address Address) int64 {
|
||||||
total := atomic.LoadUint32(&t.split)
|
total := atomic.LoadInt64(&t.split)
|
||||||
atomic.StoreUint32(&t.total, total)
|
atomic.StoreInt64(&t.total, total)
|
||||||
t.Address = address
|
t.Address = address
|
||||||
return int(total)
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status returns the value of state and the total count
|
// Status returns the value of state and the total count
|
||||||
func (t *Tag) Status(state State) (int, int, error) {
|
func (t *Tag) Status(state State) (int64, int64, error) {
|
||||||
count, seen, total := t.Get(state), int(atomic.LoadUint32(&t.seen)), int(atomic.LoadUint32(&t.total))
|
count, seen, total := t.Get(state), atomic.LoadInt64(&t.seen), atomic.LoadInt64(&t.total)
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
return count, total, errNA
|
return count, total, errNA
|
||||||
}
|
}
|
||||||
switch state {
|
switch state {
|
||||||
case SPLIT, STORED, SEEN:
|
case StateSplit, StateStored, StateSeen:
|
||||||
return count, total, nil
|
return count, total, nil
|
||||||
case SENT, SYNCED:
|
case StateSent, StateSynced:
|
||||||
stored := int(atomic.LoadUint32(&t.stored))
|
stored := atomic.LoadInt64(&t.stored)
|
||||||
if stored < total {
|
if stored < total {
|
||||||
return count, total - seen, errNA
|
return count, total - seen, errNA
|
||||||
}
|
}
|
||||||
@ -152,14 +152,14 @@ func (t *Tag) ETA(state State) (time.Time, error) {
|
|||||||
|
|
||||||
// MarshalBinary marshals the tag into a byte slice
|
// MarshalBinary marshals the tag into a byte slice
|
||||||
func (tag *Tag) MarshalBinary() (data []byte, err error) {
|
func (tag *Tag) MarshalBinary() (data []byte, err error) {
|
||||||
buffer := make([]byte, 0)
|
buffer := make([]byte, 4)
|
||||||
encodeUint32Append(&buffer, tag.Uid)
|
binary.BigEndian.PutUint32(buffer, tag.Uid)
|
||||||
encodeUint32Append(&buffer, tag.total)
|
encodeInt64Append(&buffer, tag.total)
|
||||||
encodeUint32Append(&buffer, tag.split)
|
encodeInt64Append(&buffer, tag.split)
|
||||||
encodeUint32Append(&buffer, tag.seen)
|
encodeInt64Append(&buffer, tag.seen)
|
||||||
encodeUint32Append(&buffer, tag.stored)
|
encodeInt64Append(&buffer, tag.stored)
|
||||||
encodeUint32Append(&buffer, tag.sent)
|
encodeInt64Append(&buffer, tag.sent)
|
||||||
encodeUint32Append(&buffer, tag.synced)
|
encodeInt64Append(&buffer, tag.synced)
|
||||||
|
|
||||||
intBuffer := make([]byte, 8)
|
intBuffer := make([]byte, 8)
|
||||||
|
|
||||||
@ -181,14 +181,15 @@ func (tag *Tag) UnmarshalBinary(buffer []byte) error {
|
|||||||
if len(buffer) < 13 {
|
if len(buffer) < 13 {
|
||||||
return errors.New("buffer too short")
|
return errors.New("buffer too short")
|
||||||
}
|
}
|
||||||
|
tag.Uid = binary.BigEndian.Uint32(buffer)
|
||||||
|
buffer = buffer[4:]
|
||||||
|
|
||||||
tag.Uid = decodeUint32Splice(&buffer)
|
tag.total = decodeInt64Splice(&buffer)
|
||||||
tag.total = decodeUint32Splice(&buffer)
|
tag.split = decodeInt64Splice(&buffer)
|
||||||
tag.split = decodeUint32Splice(&buffer)
|
tag.seen = decodeInt64Splice(&buffer)
|
||||||
tag.seen = decodeUint32Splice(&buffer)
|
tag.stored = decodeInt64Splice(&buffer)
|
||||||
tag.stored = decodeUint32Splice(&buffer)
|
tag.sent = decodeInt64Splice(&buffer)
|
||||||
tag.sent = decodeUint32Splice(&buffer)
|
tag.synced = decodeInt64Splice(&buffer)
|
||||||
tag.synced = decodeUint32Splice(&buffer)
|
|
||||||
|
|
||||||
t, n := binary.Varint(buffer)
|
t, n := binary.Varint(buffer)
|
||||||
tag.startedAt = time.Unix(t, 0)
|
tag.startedAt = time.Unix(t, 0)
|
||||||
@ -202,17 +203,16 @@ func (tag *Tag) UnmarshalBinary(buffer []byte) error {
|
|||||||
tag.Name = string(buffer[t:])
|
tag.Name = string(buffer[t:])
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeUint32Append(buffer *[]byte, val uint32) {
|
func encodeInt64Append(buffer *[]byte, val int64) {
|
||||||
intBuffer := make([]byte, 4)
|
intBuffer := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint32(intBuffer, val)
|
n := binary.PutVarint(intBuffer, val)
|
||||||
*buffer = append(*buffer, intBuffer...)
|
*buffer = append(*buffer, intBuffer[:n]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeUint32Splice(buffer *[]byte) uint32 {
|
func decodeInt64Splice(buffer *[]byte) int64 {
|
||||||
val := binary.BigEndian.Uint32((*buffer)[:4])
|
val, n := binary.Varint((*buffer))
|
||||||
*buffer = (*buffer)[4:]
|
*buffer = (*buffer)[n:]
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
allStates = []State{SPLIT, STORED, SEEN, SENT, SYNCED}
|
allStates = []State{StateSplit, StateStored, StateSeen, StateSent, StateSynced}
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestTagSingleIncrements tests if Inc increments the tag state value
|
// TestTagSingleIncrements tests if Inc increments the tag state value
|
||||||
@ -34,14 +34,14 @@ func TestTagSingleIncrements(t *testing.T) {
|
|||||||
tc := []struct {
|
tc := []struct {
|
||||||
state uint32
|
state uint32
|
||||||
inc int
|
inc int
|
||||||
expcount int
|
expcount int64
|
||||||
exptotal int
|
exptotal int64
|
||||||
}{
|
}{
|
||||||
{state: SPLIT, inc: 10, expcount: 10, exptotal: 10},
|
{state: StateSplit, inc: 10, expcount: 10, exptotal: 10},
|
||||||
{state: STORED, inc: 9, expcount: 9, exptotal: 9},
|
{state: StateStored, inc: 9, expcount: 9, exptotal: 9},
|
||||||
{state: SEEN, inc: 1, expcount: 1, exptotal: 10},
|
{state: StateSeen, inc: 1, expcount: 1, exptotal: 10},
|
||||||
{state: SENT, inc: 9, expcount: 9, exptotal: 9},
|
{state: StateSent, inc: 9, expcount: 9, exptotal: 9},
|
||||||
{state: SYNCED, inc: 9, expcount: 9, exptotal: 9},
|
{state: StateSynced, inc: 9, expcount: 9, exptotal: 9},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tc {
|
for _, tc := range tc {
|
||||||
@ -60,24 +60,24 @@ func TestTagSingleIncrements(t *testing.T) {
|
|||||||
// TestTagStatus is a unit test to cover Tag.Status method functionality
|
// TestTagStatus is a unit test to cover Tag.Status method functionality
|
||||||
func TestTagStatus(t *testing.T) {
|
func TestTagStatus(t *testing.T) {
|
||||||
tg := &Tag{total: 10}
|
tg := &Tag{total: 10}
|
||||||
tg.Inc(SEEN)
|
tg.Inc(StateSeen)
|
||||||
tg.Inc(SENT)
|
tg.Inc(StateSent)
|
||||||
tg.Inc(SYNCED)
|
tg.Inc(StateSynced)
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
tg.Inc(SPLIT)
|
tg.Inc(StateSplit)
|
||||||
tg.Inc(STORED)
|
tg.Inc(StateStored)
|
||||||
}
|
}
|
||||||
for _, v := range []struct {
|
for _, v := range []struct {
|
||||||
state State
|
state State
|
||||||
expVal int
|
expVal int64
|
||||||
expTotal int
|
expTotal int64
|
||||||
}{
|
}{
|
||||||
{state: STORED, expVal: 10, expTotal: 10},
|
{state: StateStored, expVal: 10, expTotal: 10},
|
||||||
{state: SPLIT, expVal: 10, expTotal: 10},
|
{state: StateSplit, expVal: 10, expTotal: 10},
|
||||||
{state: SEEN, expVal: 1, expTotal: 10},
|
{state: StateSeen, expVal: 1, expTotal: 10},
|
||||||
{state: SENT, expVal: 1, expTotal: 9},
|
{state: StateSent, expVal: 1, expTotal: 9},
|
||||||
{state: SYNCED, expVal: 1, expTotal: 9},
|
{state: StateSynced, expVal: 1, expTotal: 9},
|
||||||
} {
|
} {
|
||||||
val, total, err := tg.Status(v.state)
|
val, total, err := tg.Status(v.state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -98,8 +98,8 @@ func TestTagETA(t *testing.T) {
|
|||||||
maxDiff := 100000 // 100 microsecond
|
maxDiff := 100000 // 100 microsecond
|
||||||
tg := &Tag{total: 10, startedAt: now}
|
tg := &Tag{total: 10, startedAt: now}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
tg.Inc(SPLIT)
|
tg.Inc(StateSplit)
|
||||||
eta, err := tg.ETA(SPLIT)
|
eta, err := tg.ETA(StateSplit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ func TestTagConcurrentIncrements(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
for _, f := range allStates {
|
for _, f := range allStates {
|
||||||
v := tg.Get(f)
|
v := tg.Get(f)
|
||||||
if v != n {
|
if v != int64(n) {
|
||||||
t.Fatalf("expected state %v to be %v, got %v", f, n, v)
|
t.Fatalf("expected state %v to be %v, got %v", f, n, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ func TestTagsMultipleConcurrentIncrementsSyncMap(t *testing.T) {
|
|||||||
wg.Add(10 * 5 * n)
|
wg.Add(10 * 5 * n)
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
s := string([]byte{uint8(i)})
|
s := string([]byte{uint8(i)})
|
||||||
tag, err := ts.New(s, n)
|
tag, err := ts.New(s, int64(n))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ func TestTagsMultipleConcurrentIncrementsSyncMap(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
stateVal := tag.Get(f)
|
stateVal := tag.Get(f)
|
||||||
if stateVal != n {
|
if stateVal != int64(n) {
|
||||||
t.Fatalf("expected tag %v state %v to be %v, got %v", uid, f, n, v)
|
t.Fatalf("expected tag %v state %v to be %v, got %v", uid, f, n, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,12 @@ func NewTags() *Tags {
|
|||||||
|
|
||||||
// New creates a new tag, stores it by the name and returns it
|
// New creates a new tag, stores it by the name and returns it
|
||||||
// it returns an error if the tag with this name already exists
|
// it returns an error if the tag with this name already exists
|
||||||
func (ts *Tags) New(s string, total int) (*Tag, error) {
|
func (ts *Tags) New(s string, total int64) (*Tag, error) {
|
||||||
t := &Tag{
|
t := &Tag{
|
||||||
Uid: ts.rng.Uint32(),
|
Uid: ts.rng.Uint32(),
|
||||||
Name: s,
|
Name: s,
|
||||||
startedAt: time.Now(),
|
startedAt: time.Now(),
|
||||||
total: uint32(total),
|
total: total,
|
||||||
}
|
}
|
||||||
if _, loaded := ts.tags.LoadOrStore(t.Uid, t); loaded {
|
if _, loaded := ts.tags.LoadOrStore(t.Uid, t); loaded {
|
||||||
return nil, errExists
|
return nil, errExists
|
||||||
@ -55,6 +55,18 @@ func (ts *Tags) New(s string, total int) (*Tag, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All returns all existing tags in Tags' sync.Map
|
||||||
|
// Note that tags are returned in no particular order
|
||||||
|
func (ts *Tags) All() (t []*Tag) {
|
||||||
|
ts.tags.Range(func(k, v interface{}) bool {
|
||||||
|
t = append(t, v.(*Tag))
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
// Get returns the undelying tag for the uid or an error if not found
|
// Get returns the undelying tag for the uid or an error if not found
|
||||||
func (ts *Tags) Get(uid uint32) (*Tag, error) {
|
func (ts *Tags) Get(uid uint32) (*Tag, error) {
|
||||||
t, ok := ts.tags.Load(uid)
|
t, ok := ts.tags.Load(uid)
|
||||||
@ -64,8 +76,8 @@ func (ts *Tags) Get(uid uint32) (*Tag, error) {
|
|||||||
return t.(*Tag), nil
|
return t.(*Tag), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContext gets a tag from the tag uid stored in the context
|
// GetFromContext gets a tag from the tag uid stored in the context
|
||||||
func (ts *Tags) GetContext(ctx context.Context) (*Tag, error) {
|
func (ts *Tags) GetFromContext(ctx context.Context) (*Tag, error) {
|
||||||
uid := sctx.GetTag(ctx)
|
uid := sctx.GetTag(ctx)
|
||||||
t, ok := ts.tags.Load(uid)
|
t, ok := ts.tags.Load(uid)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -78,3 +90,7 @@ func (ts *Tags) GetContext(ctx context.Context) (*Tag, error) {
|
|||||||
func (ts *Tags) Range(fn func(k, v interface{}) bool) {
|
func (ts *Tags) Range(fn func(k, v interface{}) bool) {
|
||||||
ts.tags.Range(fn)
|
ts.tags.Range(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ts *Tags) Delete(k interface{}) {
|
||||||
|
ts.tags.Delete(k)
|
||||||
|
}
|
||||||
|
48
swarm/chunk/tags_test.go
Normal file
48
swarm/chunk/tags_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package chunk
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
ts := NewTags()
|
||||||
|
|
||||||
|
ts.New("1", 1)
|
||||||
|
ts.New("2", 1)
|
||||||
|
|
||||||
|
all := ts.All()
|
||||||
|
|
||||||
|
if len(all) != 2 {
|
||||||
|
t.Fatalf("expected length to be 2 got %d", len(all))
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := all[0].Total(); n != 1 {
|
||||||
|
t.Fatalf("expected tag 0 total to be 1 got %d", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n := all[1].Total(); n != 1 {
|
||||||
|
t.Fatalf("expected tag 1 total to be 1 got %d", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
ts.New("3", 1)
|
||||||
|
all = ts.All()
|
||||||
|
|
||||||
|
if len(all) != 3 {
|
||||||
|
t.Fatalf("expected length to be 3 got %d", len(all))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
colorable "github.com/mattn/go-colorable"
|
colorable "github.com/mattn/go-colorable"
|
||||||
@ -1614,11 +1615,11 @@ func TestFUSE(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer os.RemoveAll(datadir)
|
defer os.RemoveAll(datadir)
|
||||||
|
|
||||||
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32))
|
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32), chunk.NewTags())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
ta := &testAPI{api: api.NewAPI(fileStore, nil, nil, nil)}
|
ta := &testAPI{api: api.NewAPI(fileStore, nil, nil, nil, chunk.NewTags())}
|
||||||
|
|
||||||
//run a short suite of tests
|
//run a short suite of tests
|
||||||
//approx time: 28s
|
//approx time: 28s
|
||||||
|
@ -127,7 +127,7 @@ func netStoreAndDeliveryWithAddr(ctx *adapters.ServiceContext, bucket *sync.Map,
|
|||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
|
|
||||||
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||||
delivery := NewDelivery(kad, netStore)
|
delivery := NewDelivery(kad, netStore)
|
||||||
|
@ -380,7 +380,7 @@ func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool)
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
//...which then gets passed to the round-robin file store
|
//...which then gets passed to the round-robin file store
|
||||||
roundRobinFileStore := storage.NewFileStore(newRoundRobinStore(stores...), storage.NewFileStoreParams())
|
roundRobinFileStore := storage.NewFileStore(newRoundRobinStore(stores...), storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
//now we can actually upload a (random) file to the round-robin store
|
//now we can actually upload a (random) file to the round-robin store
|
||||||
size := chunkCount * chunkSize
|
size := chunkCount * chunkSize
|
||||||
log.Debug("Storing data to file store")
|
log.Debug("Storing data to file store")
|
||||||
|
@ -298,7 +298,7 @@ func mapKeysToNodes(conf *synctestConfig) {
|
|||||||
//upload a file(chunks) to a single local node store
|
//upload a file(chunks) to a single local node store
|
||||||
func uploadFileToSingleNodeStore(id enode.ID, chunkCount int, store chunk.Store) ([]storage.Address, error) {
|
func uploadFileToSingleNodeStore(id enode.ID, chunkCount int, store chunk.Store) ([]storage.Address, error) {
|
||||||
log.Debug(fmt.Sprintf("Uploading to node id: %s", id))
|
log.Debug(fmt.Sprintf("Uploading to node id: %s", id))
|
||||||
fileStore := storage.NewFileStore(store, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(store, storage.NewFileStoreParams(), chunk.NewTags())
|
||||||
size := chunkSize
|
size := chunkSize
|
||||||
var rootAddrs []storage.Address
|
var rootAddrs []storage.Address
|
||||||
for i := 0; i < chunkCount; i++ {
|
for i := 0; i < chunkCount; i++ {
|
||||||
|
@ -23,11 +23,13 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
@ -416,7 +418,7 @@ func uploadFile(swarm *Swarm) (storage.Address, string, error) {
|
|||||||
// uniqueness is very certain.
|
// uniqueness is very certain.
|
||||||
data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b)
|
data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b)
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
k, wait, err := swarm.api.Put(ctx, data, "text/plain", false)
|
k, wait, err := putString(ctx, swarm.api, data, "text/plain", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
@ -530,3 +532,31 @@ func retrieve(
|
|||||||
|
|
||||||
return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount)
|
return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// putString provides singleton manifest creation on top of api.API
|
||||||
|
func putString(ctx context.Context, a *api.API, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) {
|
||||||
|
r := strings.NewReader(content)
|
||||||
|
tag, err := a.Tags.New("unnamed-tag", 0)
|
||||||
|
|
||||||
|
log.Trace("created new tag", "uid", tag.Uid)
|
||||||
|
|
||||||
|
cCtx := sctx.SetTag(ctx, tag.Uid)
|
||||||
|
key, waitContent, err := a.Store(cCtx, r, int64(len(content)), toEncrypt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType)
|
||||||
|
r = strings.NewReader(manifest)
|
||||||
|
key, waitManifest, err := a.Store(cCtx, r, int64(len(manifest)), toEncrypt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
tag.DoneSplit(key)
|
||||||
|
return key, func(ctx context.Context) error {
|
||||||
|
err := waitContent(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return waitManifest(ctx)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
)
|
)
|
||||||
@ -42,8 +43,10 @@ type chunkerTester struct {
|
|||||||
t test
|
t test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mockTag = chunk.NewTag(0, "mock-tag", 0)
|
||||||
|
|
||||||
func newTestHasherStore(store ChunkStore, hash string) *hasherStore {
|
func newTestHasherStore(store ChunkStore, hash string) *hasherStore {
|
||||||
return NewHasherStore(store, MakeHashFunc(hash), false)
|
return NewHasherStore(store, MakeHashFunc(hash), false, chunk.NewTag(0, "test-tag", 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRandomBrokenData(n int, tester *chunkerTester) {
|
func testRandomBrokenData(n int, tester *chunkerTester) {
|
||||||
@ -91,7 +94,7 @@ func testRandomData(usePyramid bool, hash string, n int, tester *chunkerTester)
|
|||||||
var err error
|
var err error
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
if usePyramid {
|
if usePyramid {
|
||||||
addr, wait, err = PyramidSplit(ctx, data, putGetter, putGetter)
|
addr, wait, err = PyramidSplit(ctx, data, putGetter, putGetter, mockTag)
|
||||||
} else {
|
} else {
|
||||||
addr, wait, err = TreeSplit(ctx, data, int64(n), putGetter)
|
addr, wait, err = TreeSplit(ctx, data, int64(n), putGetter)
|
||||||
}
|
}
|
||||||
@ -188,7 +191,7 @@ func TestDataAppend(t *testing.T) {
|
|||||||
putGetter := newTestHasherStore(store, SHA3Hash)
|
putGetter := newTestHasherStore(store, SHA3Hash)
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
addr, wait, err := PyramidSplit(ctx, data, putGetter, putGetter)
|
addr, wait, err := PyramidSplit(ctx, data, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tester.t.Fatalf(err.Error())
|
tester.t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -208,7 +211,7 @@ func TestDataAppend(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
putGetter = newTestHasherStore(store, SHA3Hash)
|
putGetter = newTestHasherStore(store, SHA3Hash)
|
||||||
newAddr, wait, err := PyramidAppend(ctx, addr, appendData, putGetter, putGetter)
|
newAddr, wait, err := PyramidAppend(ctx, addr, appendData, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tester.t.Fatalf(err.Error())
|
tester.t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -278,7 +281,7 @@ func benchmarkSplitJoin(n int, t *testing.B) {
|
|||||||
|
|
||||||
putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash)
|
putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash)
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
key, wait, err := PyramidSplit(ctx, data, putGetter, putGetter)
|
key, wait, err := PyramidSplit(ctx, data, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -335,7 +338,7 @@ func benchmarkSplitPyramidBMT(n int, t *testing.B) {
|
|||||||
putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash)
|
putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, wait, err := PyramidSplit(ctx, data, putGetter, putGetter)
|
_, wait, err := PyramidSplit(ctx, data, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -353,7 +356,7 @@ func benchmarkSplitPyramidSHA3(n int, t *testing.B) {
|
|||||||
putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash)
|
putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, wait, err := PyramidSplit(ctx, data, putGetter, putGetter)
|
_, wait, err := PyramidSplit(ctx, data, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -374,7 +377,7 @@ func benchmarkSplitAppendPyramid(n, m int, t *testing.B) {
|
|||||||
putGetter := newTestHasherStore(store, SHA3Hash)
|
putGetter := newTestHasherStore(store, SHA3Hash)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
key, wait, err := PyramidSplit(ctx, data, putGetter, putGetter)
|
key, wait, err := PyramidSplit(ctx, data, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
@ -384,7 +387,7 @@ func benchmarkSplitAppendPyramid(n, m int, t *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
putGetter = newTestHasherStore(store, SHA3Hash)
|
putGetter = newTestHasherStore(store, SHA3Hash)
|
||||||
_, wait, err = PyramidAppend(ctx, key, data1, putGetter, putGetter)
|
_, wait, err = PyramidAppend(ctx, key, data1, putGetter, putGetter, mockTag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf(err.Error())
|
t.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,7 @@ const (
|
|||||||
type FileStore struct {
|
type FileStore struct {
|
||||||
ChunkStore
|
ChunkStore
|
||||||
hashFunc SwarmHasher
|
hashFunc SwarmHasher
|
||||||
|
tags *chunk.Tags
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileStoreParams struct {
|
type FileStoreParams struct {
|
||||||
@ -60,19 +61,20 @@ func NewFileStoreParams() *FileStoreParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// for testing locally
|
// for testing locally
|
||||||
func NewLocalFileStore(datadir string, basekey []byte) (*FileStore, error) {
|
func NewLocalFileStore(datadir string, basekey []byte, tags *chunk.Tags) (*FileStore, error) {
|
||||||
localStore, err := localstore.New(datadir, basekey, nil)
|
localStore, err := localstore.New(datadir, basekey, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewFileStore(chunk.NewValidatorStore(localStore, NewContentAddressValidator(MakeHashFunc(DefaultHash))), NewFileStoreParams()), nil
|
return NewFileStore(chunk.NewValidatorStore(localStore, NewContentAddressValidator(MakeHashFunc(DefaultHash))), NewFileStoreParams(), tags), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFileStore(store ChunkStore, params *FileStoreParams) *FileStore {
|
func NewFileStore(store ChunkStore, params *FileStoreParams, tags *chunk.Tags) *FileStore {
|
||||||
hashFunc := MakeHashFunc(params.Hash)
|
hashFunc := MakeHashFunc(params.Hash)
|
||||||
return &FileStore{
|
return &FileStore{
|
||||||
ChunkStore: store,
|
ChunkStore: store,
|
||||||
hashFunc: hashFunc,
|
hashFunc: hashFunc,
|
||||||
|
tags: tags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +85,11 @@ func NewFileStore(store ChunkStore, params *FileStoreParams) *FileStore {
|
|||||||
// It returns a reader with the chunk data and whether the content was encrypted
|
// It returns a reader with the chunk data and whether the content was encrypted
|
||||||
func (f *FileStore) Retrieve(ctx context.Context, addr Address) (reader *LazyChunkReader, isEncrypted bool) {
|
func (f *FileStore) Retrieve(ctx context.Context, addr Address) (reader *LazyChunkReader, isEncrypted bool) {
|
||||||
isEncrypted = len(addr) > f.hashFunc().Size()
|
isEncrypted = len(addr) > f.hashFunc().Size()
|
||||||
getter := NewHasherStore(f.ChunkStore, f.hashFunc, isEncrypted)
|
tag, err := f.tags.GetFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
tag = chunk.NewTag(0, "ephemeral-retrieval-tag", 0)
|
||||||
|
}
|
||||||
|
getter := NewHasherStore(f.ChunkStore, f.hashFunc, isEncrypted, tag)
|
||||||
reader = TreeJoin(ctx, addr, getter, 0)
|
reader = TreeJoin(ctx, addr, getter, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -91,8 +97,17 @@ func (f *FileStore) Retrieve(ctx context.Context, addr Address) (reader *LazyChu
|
|||||||
// Store is a public API. Main entry point for document storage directly. Used by the
|
// Store is a public API. Main entry point for document storage directly. Used by the
|
||||||
// FS-aware API and httpaccess
|
// FS-aware API and httpaccess
|
||||||
func (f *FileStore) Store(ctx context.Context, data io.Reader, size int64, toEncrypt bool) (addr Address, wait func(context.Context) error, err error) {
|
func (f *FileStore) Store(ctx context.Context, data io.Reader, size int64, toEncrypt bool) (addr Address, wait func(context.Context) error, err error) {
|
||||||
putter := NewHasherStore(f.ChunkStore, f.hashFunc, toEncrypt)
|
tag, err := f.tags.GetFromContext(ctx)
|
||||||
return PyramidSplit(ctx, data, putter, putter)
|
if err != nil {
|
||||||
|
// some of the parts of the codebase, namely the manifest trie, do not store the context
|
||||||
|
// of the original request nor the tag with the trie, recalculating the trie hence
|
||||||
|
// loses the tag uid. thus we create an ephemeral tag here for that purpose
|
||||||
|
|
||||||
|
tag = chunk.NewTag(0, "", 0)
|
||||||
|
//return nil, nil, err
|
||||||
|
}
|
||||||
|
putter := NewHasherStore(f.ChunkStore, f.hashFunc, toEncrypt, tag)
|
||||||
|
return PyramidSplit(ctx, data, putter, putter, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileStore) HashSize() int {
|
func (f *FileStore) HashSize() int {
|
||||||
@ -101,12 +116,14 @@ func (f *FileStore) HashSize() int {
|
|||||||
|
|
||||||
// GetAllReferences is a public API. This endpoint returns all chunk hashes (only) for a given file
|
// GetAllReferences is a public API. This endpoint returns all chunk hashes (only) for a given file
|
||||||
func (f *FileStore) GetAllReferences(ctx context.Context, data io.Reader, toEncrypt bool) (addrs AddressCollection, err error) {
|
func (f *FileStore) GetAllReferences(ctx context.Context, data io.Reader, toEncrypt bool) (addrs AddressCollection, err error) {
|
||||||
|
tag := chunk.NewTag(0, "ephemeral-tag", 0) //this tag is just a mock ephemeral tag since we don't want to save these results
|
||||||
|
|
||||||
// create a special kind of putter, which only will store the references
|
// create a special kind of putter, which only will store the references
|
||||||
putter := &hashExplorer{
|
putter := &hashExplorer{
|
||||||
hasherStore: NewHasherStore(f.ChunkStore, f.hashFunc, toEncrypt),
|
hasherStore: NewHasherStore(f.ChunkStore, f.hashFunc, toEncrypt, tag),
|
||||||
}
|
}
|
||||||
// do the actual splitting anyway, no way around it
|
// do the actual splitting anyway, no way around it
|
||||||
_, wait, err := PyramidSplit(ctx, data, putter, putter)
|
_, wait, err := PyramidSplit(ctx, data, putter, putter, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/localstore"
|
"github.com/ethereum/go-ethereum/swarm/storage/localstore"
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
)
|
)
|
||||||
@ -48,7 +49,7 @@ func testFileStoreRandom(toEncrypt bool, t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer localStore.Close()
|
defer localStore.Close()
|
||||||
|
|
||||||
fileStore := NewFileStore(localStore, NewFileStoreParams())
|
fileStore := NewFileStore(localStore, NewFileStoreParams(), chunk.NewTags())
|
||||||
|
|
||||||
slice := testutil.RandomBytes(1, testDataSize)
|
slice := testutil.RandomBytes(1, testDataSize)
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
@ -113,7 +114,7 @@ func testFileStoreCapacity(toEncrypt bool, t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer localStore.Close()
|
defer localStore.Close()
|
||||||
|
|
||||||
fileStore := NewFileStore(localStore, NewFileStoreParams())
|
fileStore := NewFileStore(localStore, NewFileStoreParams(), chunk.NewTags())
|
||||||
slice := testutil.RandomBytes(1, testDataSize)
|
slice := testutil.RandomBytes(1, testDataSize)
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
key, wait, err := fileStore.Store(ctx, bytes.NewReader(slice), testDataSize, toEncrypt)
|
key, wait, err := fileStore.Store(ctx, bytes.NewReader(slice), testDataSize, toEncrypt)
|
||||||
@ -182,7 +183,7 @@ func TestGetAllReferences(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer localStore.Close()
|
defer localStore.Close()
|
||||||
|
|
||||||
fileStore := NewFileStore(localStore, NewFileStoreParams())
|
fileStore := NewFileStore(localStore, NewFileStoreParams(), chunk.NewTags())
|
||||||
|
|
||||||
// testRuns[i] and expectedLen[i] are dataSize and expected length respectively
|
// testRuns[i] and expectedLen[i] are dataSize and expected length respectively
|
||||||
testRuns := []int{1024, 8192, 16000, 30000, 1000000}
|
testRuns := []int{1024, 8192, 16000, 30000, 1000000}
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
type hasherStore struct {
|
type hasherStore struct {
|
||||||
store ChunkStore
|
store ChunkStore
|
||||||
|
tag *chunk.Tag
|
||||||
toEncrypt bool
|
toEncrypt bool
|
||||||
hashFunc SwarmHasher
|
hashFunc SwarmHasher
|
||||||
hashSize int // content hash size
|
hashSize int // content hash size
|
||||||
@ -44,7 +45,7 @@ type hasherStore struct {
|
|||||||
// NewHasherStore creates a hasherStore object, which implements Putter and Getter interfaces.
|
// NewHasherStore creates a hasherStore object, which implements Putter and Getter interfaces.
|
||||||
// With the HasherStore you can put and get chunk data (which is just []byte) into a ChunkStore
|
// With the HasherStore you can put and get chunk data (which is just []byte) into a ChunkStore
|
||||||
// and the hasherStore will take core of encryption/decryption of data if necessary
|
// and the hasherStore will take core of encryption/decryption of data if necessary
|
||||||
func NewHasherStore(store ChunkStore, hashFunc SwarmHasher, toEncrypt bool) *hasherStore {
|
func NewHasherStore(store ChunkStore, hashFunc SwarmHasher, toEncrypt bool, tag *chunk.Tag) *hasherStore {
|
||||||
hashSize := hashFunc().Size()
|
hashSize := hashFunc().Size()
|
||||||
refSize := int64(hashSize)
|
refSize := int64(hashSize)
|
||||||
if toEncrypt {
|
if toEncrypt {
|
||||||
@ -53,6 +54,7 @@ func NewHasherStore(store ChunkStore, hashFunc SwarmHasher, toEncrypt bool) *has
|
|||||||
|
|
||||||
h := &hasherStore{
|
h := &hasherStore{
|
||||||
store: store,
|
store: store,
|
||||||
|
tag: tag,
|
||||||
toEncrypt: toEncrypt,
|
toEncrypt: toEncrypt,
|
||||||
hashFunc: hashFunc,
|
hashFunc: hashFunc,
|
||||||
hashSize: hashSize,
|
hashSize: hashSize,
|
||||||
@ -242,7 +244,11 @@ func (h *hasherStore) newDataEncryption(key encryption.Key) encryption.Encryptio
|
|||||||
func (h *hasherStore) storeChunk(ctx context.Context, ch Chunk) {
|
func (h *hasherStore) storeChunk(ctx context.Context, ch Chunk) {
|
||||||
atomic.AddUint64(&h.nrChunks, 1)
|
atomic.AddUint64(&h.nrChunks, 1)
|
||||||
go func() {
|
go func() {
|
||||||
_, err := h.store.Put(ctx, chunk.ModePutUpload, ch)
|
seen, err := h.store.Put(ctx, chunk.ModePutUpload, ch)
|
||||||
|
h.tag.Inc(chunk.StateStored)
|
||||||
|
if seen {
|
||||||
|
h.tag.Inc(chunk.StateSeen)
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case h.errC <- err:
|
case h.errC <- err:
|
||||||
case <-h.quitC:
|
case <-h.quitC:
|
||||||
|
@ -43,7 +43,7 @@ func TestHasherStore(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
chunkStore := NewMapChunkStore()
|
chunkStore := NewMapChunkStore()
|
||||||
hasherStore := NewHasherStore(chunkStore, MakeHashFunc(DefaultHash), tt.toEncrypt)
|
hasherStore := NewHasherStore(chunkStore, MakeHashFunc(DefaultHash), tt.toEncrypt, chunk.NewTag(0, "test-tag", 0))
|
||||||
|
|
||||||
// Put two random chunks into the hasherStore
|
// Put two random chunks into the hasherStore
|
||||||
chunkData1 := GenerateRandomChunk(int64(tt.chunkLength)).Data()
|
chunkData1 := GenerateRandomChunk(int64(tt.chunkLength)).Data()
|
||||||
|
@ -96,12 +96,12 @@ func NewPyramidSplitterParams(addr Address, reader io.Reader, putter Putter, get
|
|||||||
When splitting, data is given as a SectionReader, and the key is a hashSize long byte slice (Address), the root hash of the entire content will fill this once processing finishes.
|
When splitting, data is given as a SectionReader, and the key is a hashSize long byte slice (Address), the root hash of the entire content will fill this once processing finishes.
|
||||||
New chunks to store are store using the putter which the caller provides.
|
New chunks to store are store using the putter which the caller provides.
|
||||||
*/
|
*/
|
||||||
func PyramidSplit(ctx context.Context, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) {
|
func PyramidSplit(ctx context.Context, reader io.Reader, putter Putter, getter Getter, tag *chunk.Tag) (Address, func(context.Context) error, error) {
|
||||||
return NewPyramidSplitter(NewPyramidSplitterParams(nil, reader, putter, getter, chunk.DefaultSize)).Split(ctx)
|
return NewPyramidSplitter(NewPyramidSplitterParams(nil, reader, putter, getter, chunk.DefaultSize), tag).Split(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PyramidAppend(ctx context.Context, addr Address, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) {
|
func PyramidAppend(ctx context.Context, addr Address, reader io.Reader, putter Putter, getter Getter, tag *chunk.Tag) (Address, func(context.Context) error, error) {
|
||||||
return NewPyramidSplitter(NewPyramidSplitterParams(addr, reader, putter, getter, chunk.DefaultSize)).Append(ctx)
|
return NewPyramidSplitter(NewPyramidSplitterParams(addr, reader, putter, getter, chunk.DefaultSize), tag).Append(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry to create a tree node
|
// Entry to create a tree node
|
||||||
@ -142,6 +142,7 @@ type PyramidChunker struct {
|
|||||||
putter Putter
|
putter Putter
|
||||||
getter Getter
|
getter Getter
|
||||||
key Address
|
key Address
|
||||||
|
tag *chunk.Tag
|
||||||
workerCount int64
|
workerCount int64
|
||||||
workerLock sync.RWMutex
|
workerLock sync.RWMutex
|
||||||
jobC chan *chunkJob
|
jobC chan *chunkJob
|
||||||
@ -152,7 +153,7 @@ type PyramidChunker struct {
|
|||||||
chunkLevel [][]*TreeEntry
|
chunkLevel [][]*TreeEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPyramidSplitter(params *PyramidSplitterParams) (pc *PyramidChunker) {
|
func NewPyramidSplitter(params *PyramidSplitterParams, tag *chunk.Tag) (pc *PyramidChunker) {
|
||||||
pc = &PyramidChunker{}
|
pc = &PyramidChunker{}
|
||||||
pc.reader = params.reader
|
pc.reader = params.reader
|
||||||
pc.hashSize = params.hashSize
|
pc.hashSize = params.hashSize
|
||||||
@ -161,6 +162,7 @@ func NewPyramidSplitter(params *PyramidSplitterParams) (pc *PyramidChunker) {
|
|||||||
pc.putter = params.putter
|
pc.putter = params.putter
|
||||||
pc.getter = params.getter
|
pc.getter = params.getter
|
||||||
pc.key = params.addr
|
pc.key = params.addr
|
||||||
|
pc.tag = tag
|
||||||
pc.workerCount = 0
|
pc.workerCount = 0
|
||||||
pc.jobC = make(chan *chunkJob, 2*ChunkProcessors)
|
pc.jobC = make(chan *chunkJob, 2*ChunkProcessors)
|
||||||
pc.wg = &sync.WaitGroup{}
|
pc.wg = &sync.WaitGroup{}
|
||||||
@ -273,6 +275,7 @@ func (pc *PyramidChunker) processor(ctx context.Context, id int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pc.processChunk(ctx, id, job)
|
pc.processChunk(ctx, id, job)
|
||||||
|
pc.tag.Inc(chunk.StateSplit)
|
||||||
case <-pc.quitC:
|
case <-pc.quitC:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -211,9 +211,10 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
|
|||||||
MaxPeerServers: config.MaxStreamPeerServers,
|
MaxPeerServers: config.MaxStreamPeerServers,
|
||||||
}
|
}
|
||||||
self.streamer = stream.NewRegistry(nodeID, delivery, self.netStore, self.stateStore, registryOptions, self.swap)
|
self.streamer = stream.NewRegistry(nodeID, delivery, self.netStore, self.stateStore, registryOptions, self.swap)
|
||||||
|
tags := chunk.NewTags() //todo load from state store
|
||||||
|
|
||||||
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
|
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
|
||||||
self.fileStore = storage.NewFileStore(self.netStore, self.config.FileStoreParams)
|
self.fileStore = storage.NewFileStore(self.netStore, self.config.FileStoreParams, tags)
|
||||||
|
|
||||||
log.Debug("Setup local storage")
|
log.Debug("Setup local storage")
|
||||||
|
|
||||||
@ -228,7 +229,7 @@ func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err e
|
|||||||
pss.SetHandshakeController(self.ps, pss.NewHandshakeParams())
|
pss.SetHandshakeController(self.ps, pss.NewHandshakeParams())
|
||||||
}
|
}
|
||||||
|
|
||||||
self.api = api.NewAPI(self.fileStore, self.dns, feedsHandler, self.privateKey)
|
self.api = api.NewAPI(self.fileStore, self.dns, feedsHandler, self.privateKey, tags)
|
||||||
|
|
||||||
self.sfs = fuse.NewSwarmFS(self.api)
|
self.sfs = fuse.NewSwarmFS(self.api)
|
||||||
log.Debug("Initialized FUSE filesystem")
|
log.Debug("Initialized FUSE filesystem")
|
||||||
|
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestNewSwarm validates Swarm fields in repsect to the provided configuration.
|
// TestNewSwarm validates Swarm fields in repsect to the provided configuration.
|
||||||
@ -352,8 +353,11 @@ func testLocalStoreAndRetrieve(t *testing.T, swarm *Swarm, n int, randomData boo
|
|||||||
rand.Read(slice)
|
rand.Read(slice)
|
||||||
}
|
}
|
||||||
dataPut := string(slice)
|
dataPut := string(slice)
|
||||||
|
tag, err := swarm.api.Tags.New("test-local-store-and-retrieve", 0)
|
||||||
ctx := context.TODO()
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ctx := sctx.SetTag(context.Background(), tag.Uid)
|
||||||
k, wait, err := swarm.api.Store(ctx, strings.NewReader(dataPut), int64(len(dataPut)), false)
|
k, wait, err := swarm.api.Store(ctx, strings.NewReader(dataPut), int64(len(dataPut)), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
51
swarm/testutil/tag.go
Normal file
51
swarm/testutil/tag.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2019 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckTag checks the first tag in the api struct to be in a certain state
|
||||||
|
func CheckTag(t *testing.T, tag *chunk.Tag, split, stored, seen, total int64) {
|
||||||
|
t.Helper()
|
||||||
|
if tag == nil {
|
||||||
|
t.Fatal("no tag found")
|
||||||
|
}
|
||||||
|
|
||||||
|
tSplit := tag.Get(chunk.StateSplit)
|
||||||
|
if tSplit != split {
|
||||||
|
t.Fatalf("should have had split chunks, got %d want %d", tSplit, split)
|
||||||
|
}
|
||||||
|
|
||||||
|
tSeen := tag.Get(chunk.StateSeen)
|
||||||
|
if tSeen != seen {
|
||||||
|
t.Fatalf("should have had seen chunks, got %d want %d", tSeen, seen)
|
||||||
|
}
|
||||||
|
|
||||||
|
tStored := tag.Get(chunk.StateStored)
|
||||||
|
if tStored != stored {
|
||||||
|
t.Fatalf("mismatch stored chunks, got %d want %d", tStored, stored)
|
||||||
|
}
|
||||||
|
|
||||||
|
tTotal := tag.Total()
|
||||||
|
if tTotal != total {
|
||||||
|
t.Fatalf("mismatch total chunks, got %d want %d", tTotal, total)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user