diff options
author | ethersphere <thesw@rm.eth> | 2018-06-20 20:06:27 +0800 |
---|---|---|
committer | ethersphere <thesw@rm.eth> | 2018-06-22 03:10:31 +0800 |
commit | e187711c6545487d4cac3701f0f506bb536234e2 (patch) | |
tree | d2f6150f70b84b36e49a449082aeda267b4b9046 /swarm/storage/mock | |
parent | 574378edb50c907b532946a1d4654dbd6701b20a (diff) | |
download | go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.tar go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.tar.gz go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.tar.bz2 go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.tar.lz go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.tar.xz go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.tar.zst go-tangerine-e187711c6545487d4cac3701f0f506bb536234e2.zip |
swarm: network rewrite merge
Diffstat (limited to 'swarm/storage/mock')
-rw-r--r-- | swarm/storage/mock/db/db.go | 236 | ||||
-rw-r--r-- | swarm/storage/mock/db/db_test.go | 75 | ||||
-rw-r--r-- | swarm/storage/mock/mem/mem.go | 175 | ||||
-rw-r--r-- | swarm/storage/mock/mem/mem_test.go | 36 | ||||
-rw-r--r-- | swarm/storage/mock/mock.go | 111 | ||||
-rw-r--r-- | swarm/storage/mock/rpc/rpc.go | 84 | ||||
-rw-r--r-- | swarm/storage/mock/rpc/rpc_test.go | 41 | ||||
-rw-r--r-- | swarm/storage/mock/test/test.go | 186 |
8 files changed, 944 insertions, 0 deletions
diff --git a/swarm/storage/mock/db/db.go b/swarm/storage/mock/db/db.go new file mode 100644 index 000000000..43bfa24f0 --- /dev/null +++ b/swarm/storage/mock/db/db.go @@ -0,0 +1,236 @@ +// Copyright 2018 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 db implements a mock store that keeps all chunk data in LevelDB database. +package db + +import ( + "archive/tar" + "bytes" + "encoding/json" + "io" + "io/ioutil" + + "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/util" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/swarm/storage/mock" +) + +// GlobalStore contains the LevelDB database that is storing +// chunk data for all swarm nodes. +// Closing the GlobalStore with Close method is required to +// release resources used by the database. +type GlobalStore struct { + db *leveldb.DB +} + +// NewGlobalStore creates a new instance of GlobalStore. +func NewGlobalStore(path string) (s *GlobalStore, err error) { + db, err := leveldb.OpenFile(path, nil) + if err != nil { + return nil, err + } + return &GlobalStore{ + db: db, + }, nil +} + +// Close releases the resources used by the underlying LevelDB. +func (s *GlobalStore) Close() error { + return s.db.Close() +} + +// NewNodeStore returns a new instance of NodeStore that retrieves and stores +// chunk data only for a node with address addr. +func (s *GlobalStore) NewNodeStore(addr common.Address) *mock.NodeStore { + return mock.NewNodeStore(addr, s) +} + +// Get returns chunk data if the chunk with key exists for node +// on address addr. +func (s *GlobalStore) Get(addr common.Address, key []byte) (data []byte, err error) { + has, err := s.db.Has(nodeDBKey(addr, key), nil) + if err != nil { + return nil, mock.ErrNotFound + } + if !has { + return nil, mock.ErrNotFound + } + data, err = s.db.Get(dataDBKey(key), nil) + if err == leveldb.ErrNotFound { + err = mock.ErrNotFound + } + return +} + +// Put saves the chunk data for node with address addr. +func (s *GlobalStore) Put(addr common.Address, key []byte, data []byte) error { + batch := new(leveldb.Batch) + batch.Put(nodeDBKey(addr, key), nil) + batch.Put(dataDBKey(key), data) + return s.db.Write(batch, nil) +} + +// HasKey returns whether a node with addr contains the key. +func (s *GlobalStore) HasKey(addr common.Address, key []byte) bool { + has, err := s.db.Has(nodeDBKey(addr, key), nil) + if err != nil { + has = false + } + return has +} + +// Import reads tar archive from a reader that contains exported chunk data. +// It returns the number of chunks imported and an error. +func (s *GlobalStore) Import(r io.Reader) (n int, err error) { + tr := tar.NewReader(r) + + for { + hdr, err := tr.Next() + if err != nil { + if err == io.EOF { + break + } + return n, err + } + + data, err := ioutil.ReadAll(tr) + if err != nil { + return n, err + } + + var c mock.ExportedChunk + if err = json.Unmarshal(data, &c); err != nil { + return n, err + } + + batch := new(leveldb.Batch) + for _, addr := range c.Addrs { + batch.Put(nodeDBKeyHex(addr, hdr.Name), nil) + } + + batch.Put(dataDBKey(common.Hex2Bytes(hdr.Name)), c.Data) + if err = s.db.Write(batch, nil); err != nil { + return n, err + } + + n++ + } + return n, err +} + +// Export writes to a writer a tar archive with all chunk data from +// the store. It returns the number fo chunks exported and an error. +func (s *GlobalStore) Export(w io.Writer) (n int, err error) { + tw := tar.NewWriter(w) + defer tw.Close() + + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + encoder := json.NewEncoder(buf) + + iter := s.db.NewIterator(util.BytesPrefix(nodeKeyPrefix), nil) + defer iter.Release() + + var currentKey string + var addrs []common.Address + + saveChunk := func(hexKey string) error { + key := common.Hex2Bytes(hexKey) + + data, err := s.db.Get(dataDBKey(key), nil) + if err != nil { + return err + } + + buf.Reset() + if err = encoder.Encode(mock.ExportedChunk{ + Addrs: addrs, + Data: data, + }); err != nil { + return err + } + + d := buf.Bytes() + hdr := &tar.Header{ + Name: hexKey, + Mode: 0644, + Size: int64(len(d)), + } + if err := tw.WriteHeader(hdr); err != nil { + return err + } + if _, err := tw.Write(d); err != nil { + return err + } + n++ + return nil + } + + for iter.Next() { + k := bytes.TrimPrefix(iter.Key(), nodeKeyPrefix) + i := bytes.Index(k, []byte("-")) + if i < 0 { + continue + } + hexKey := string(k[:i]) + + if currentKey == "" { + currentKey = hexKey + } + + if hexKey != currentKey { + if err = saveChunk(currentKey); err != nil { + return n, err + } + + addrs = addrs[:0] + } + + currentKey = hexKey + addrs = append(addrs, common.BytesToAddress(k[i:])) + } + + if len(addrs) > 0 { + if err = saveChunk(currentKey); err != nil { + return n, err + } + } + + return n, err +} + +var ( + nodeKeyPrefix = []byte("node-") + dataKeyPrefix = []byte("data-") +) + +// nodeDBKey constructs a database key for key/node mappings. +func nodeDBKey(addr common.Address, key []byte) []byte { + return nodeDBKeyHex(addr, common.Bytes2Hex(key)) +} + +// nodeDBKeyHex constructs a database key for key/node mappings +// using the hexadecimal string representation of the key. +func nodeDBKeyHex(addr common.Address, hexKey string) []byte { + return append(append(nodeKeyPrefix, []byte(hexKey+"-")...), addr[:]...) +} + +// dataDBkey constructs a database key for key/data storage. +func dataDBKey(key []byte) []byte { + return append(dataKeyPrefix, key...) +} diff --git a/swarm/storage/mock/db/db_test.go b/swarm/storage/mock/db/db_test.go new file mode 100644 index 000000000..782faaf35 --- /dev/null +++ b/swarm/storage/mock/db/db_test.go @@ -0,0 +1,75 @@ +// +build go1.8 +// +// Copyright 2018 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 db + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/swarm/storage/mock/test" +) + +// TestDBStore is running a test.MockStore tests +// using test.MockStore function. +func TestDBStore(t *testing.T) { + dir, err := ioutil.TempDir("", "mock_"+t.Name()) + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + + store, err := NewGlobalStore(dir) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + test.MockStore(t, store, 100) +} + +// TestImportExport is running a test.ImportExport tests +// using test.MockStore function. +func TestImportExport(t *testing.T) { + dir1, err := ioutil.TempDir("", "mock_"+t.Name()+"_exporter") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir1) + + store1, err := NewGlobalStore(dir1) + if err != nil { + t.Fatal(err) + } + defer store1.Close() + + dir2, err := ioutil.TempDir("", "mock_"+t.Name()+"_importer") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir2) + + store2, err := NewGlobalStore(dir2) + if err != nil { + t.Fatal(err) + } + defer store2.Close() + + test.ImportExport(t, store1, store2, 100) +} diff --git a/swarm/storage/mock/mem/mem.go b/swarm/storage/mock/mem/mem.go new file mode 100644 index 000000000..8878309d0 --- /dev/null +++ b/swarm/storage/mock/mem/mem.go @@ -0,0 +1,175 @@ +// Copyright 2018 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 mem implements a mock store that keeps all chunk data in memory. +// While it can be used for testing on smaller scales, the main purpose of this +// package is to provide the simplest reference implementation of a mock store. +package mem + +import ( + "archive/tar" + "bytes" + "encoding/json" + "io" + "io/ioutil" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/swarm/storage/mock" +) + +// GlobalStore stores all chunk data and also keys and node addresses relations. +// It implements mock.GlobalStore interface. +type GlobalStore struct { + nodes map[string]map[common.Address]struct{} + data map[string][]byte + mu sync.Mutex +} + +// NewGlobalStore creates a new instance of GlobalStore. +func NewGlobalStore() *GlobalStore { + return &GlobalStore{ + nodes: make(map[string]map[common.Address]struct{}), + data: make(map[string][]byte), + } +} + +// NewNodeStore returns a new instance of NodeStore that retrieves and stores +// chunk data only for a node with address addr. +func (s *GlobalStore) NewNodeStore(addr common.Address) *mock.NodeStore { + return mock.NewNodeStore(addr, s) +} + +// Get returns chunk data if the chunk with key exists for node +// on address addr. +func (s *GlobalStore) Get(addr common.Address, key []byte) (data []byte, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.nodes[string(key)][addr]; !ok { + return nil, mock.ErrNotFound + } + + data, ok := s.data[string(key)] + if !ok { + return nil, mock.ErrNotFound + } + return data, nil +} + +// Put saves the chunk data for node with address addr. +func (s *GlobalStore) Put(addr common.Address, key []byte, data []byte) error { + s.mu.Lock() + defer s.mu.Unlock() + + if _, ok := s.nodes[string(key)]; !ok { + s.nodes[string(key)] = make(map[common.Address]struct{}) + } + s.nodes[string(key)][addr] = struct{}{} + s.data[string(key)] = data + return nil +} + +// HasKey returns whether a node with addr contains the key. +func (s *GlobalStore) HasKey(addr common.Address, key []byte) bool { + s.mu.Lock() + defer s.mu.Unlock() + + _, ok := s.nodes[string(key)][addr] + return ok +} + +// Import reads tar archive from a reader that contains exported chunk data. +// It returns the number of chunks imported and an error. +func (s *GlobalStore) Import(r io.Reader) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + tr := tar.NewReader(r) + + for { + hdr, err := tr.Next() + if err != nil { + if err == io.EOF { + break + } + return n, err + } + + data, err := ioutil.ReadAll(tr) + if err != nil { + return n, err + } + + var c mock.ExportedChunk + if err = json.Unmarshal(data, &c); err != nil { + return n, err + } + + addrs := make(map[common.Address]struct{}) + for _, a := range c.Addrs { + addrs[a] = struct{}{} + } + + key := string(common.Hex2Bytes(hdr.Name)) + s.nodes[key] = addrs + s.data[key] = c.Data + n++ + } + return n, err +} + +// Export writes to a writer a tar archive with all chunk data from +// the store. It returns the number of chunks exported and an error. +func (s *GlobalStore) Export(w io.Writer) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + + tw := tar.NewWriter(w) + defer tw.Close() + + buf := bytes.NewBuffer(make([]byte, 0, 1024)) + encoder := json.NewEncoder(buf) + for key, addrs := range s.nodes { + al := make([]common.Address, 0, len(addrs)) + for a := range addrs { + al = append(al, a) + } + + buf.Reset() + if err = encoder.Encode(mock.ExportedChunk{ + Addrs: al, + Data: s.data[key], + }); err != nil { + return n, err + } + + data := buf.Bytes() + hdr := &tar.Header{ + Name: common.Bytes2Hex([]byte(key)), + Mode: 0644, + Size: int64(len(data)), + } + if err := tw.WriteHeader(hdr); err != nil { + return n, err + } + if _, err := tw.Write(data); err != nil { + return n, err + } + n++ + } + return n, err +} diff --git a/swarm/storage/mock/mem/mem_test.go b/swarm/storage/mock/mem/mem_test.go new file mode 100644 index 000000000..adcefaabb --- /dev/null +++ b/swarm/storage/mock/mem/mem_test.go @@ -0,0 +1,36 @@ +// Copyright 2018 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 mem + +import ( + "testing" + + "github.com/ethereum/go-ethereum/swarm/storage/mock/test" +) + +// TestGlobalStore is running test for a GlobalStore +// using test.MockStore function. +func TestGlobalStore(t *testing.T) { + test.MockStore(t, NewGlobalStore(), 100) +} + +// TestImportExport is running tests for importing and +// exporting data between two GlobalStores +// using test.ImportExport function. +func TestImportExport(t *testing.T) { + test.ImportExport(t, NewGlobalStore(), NewGlobalStore(), 100) +} diff --git a/swarm/storage/mock/mock.go b/swarm/storage/mock/mock.go new file mode 100644 index 000000000..81340f927 --- /dev/null +++ b/swarm/storage/mock/mock.go @@ -0,0 +1,111 @@ +// Copyright 2018 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 mock defines types that are used by different implementations +// of mock storages. +// +// Implementations of mock storages are located in directories +// under this package: +// +// - db - LevelDB backend +// - mem - in memory map backend +// - rpc - RPC client that can connect to other backends +// +// Mock storages can implement Importer and Exporter interfaces +// for importing and exporting all chunk data that they contain. +// The exported file is a tar archive with all files named by +// hexadecimal representations of chunk keys and with content +// with JSON-encoded ExportedChunk structure. Exported format +// should be preserved across all mock store implementations. +package mock + +import ( + "errors" + "io" + + "github.com/ethereum/go-ethereum/common" +) + +// ErrNotFound indicates that the chunk is not found. +var ErrNotFound = errors.New("not found") + +// NodeStore holds the node address and a reference to the GlobalStore +// in order to access and store chunk data only for one node. +type NodeStore struct { + store GlobalStorer + addr common.Address +} + +// NewNodeStore creates a new instance of NodeStore that keeps +// chunk data using GlobalStorer with a provided address. +func NewNodeStore(addr common.Address, store GlobalStorer) *NodeStore { + return &NodeStore{ + store: store, + addr: addr, + } +} + +// Get returns chunk data for a key for a node that has the address +// provided on NodeStore initialization. +func (n *NodeStore) Get(key []byte) (data []byte, err error) { + return n.store.Get(n.addr, key) +} + +// Put saves chunk data for a key for a node that has the address +// provided on NodeStore initialization. +func (n *NodeStore) Put(key []byte, data []byte) error { + return n.store.Put(n.addr, key, data) +} + +// GlobalStorer defines methods for mock db store +// that stores chunk data for all swarm nodes. +// It is used in tests to construct mock NodeStores +// for swarm nodes and to track and validate chunks. +type GlobalStorer interface { + Get(addr common.Address, key []byte) (data []byte, err error) + Put(addr common.Address, key []byte, data []byte) error + HasKey(addr common.Address, key []byte) bool + // NewNodeStore creates an instance of NodeStore + // to be used by a single swarm node with + // address addr. + NewNodeStore(addr common.Address) *NodeStore +} + +// Importer defines method for importing mock store data +// from an exported tar archive. +type Importer interface { + Import(r io.Reader) (n int, err error) +} + +// Exporter defines method for exporting mock store data +// to a tar archive. +type Exporter interface { + Export(w io.Writer) (n int, err error) +} + +// ImportExporter is an interface for importing and exporting +// mock store data to and from a tar archive. +type ImportExporter interface { + Importer + Exporter +} + +// ExportedChunk is the structure that is saved in tar archive for +// each chunk as JSON-encoded bytes. +type ExportedChunk struct { + Data []byte `json:"d"` + Addrs []common.Address `json:"a"` +} diff --git a/swarm/storage/mock/rpc/rpc.go b/swarm/storage/mock/rpc/rpc.go new file mode 100644 index 000000000..6e735f698 --- /dev/null +++ b/swarm/storage/mock/rpc/rpc.go @@ -0,0 +1,84 @@ +// Copyright 2018 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 rpc implements an RPC client that connect to a centralized mock store. +// Centralazied mock store can be any other mock store implementation that is +// registered to Ethereum RPC server under mockStore name. Methods that defines +// mock.GlobalStore are the same that are used by RPC. Example: +// +// server := rpc.NewServer() +// server.RegisterName("mockStore", mem.NewGlobalStore()) +package rpc + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/swarm/log" + "github.com/ethereum/go-ethereum/swarm/storage/mock" +) + +// GlobalStore is rpc.Client that connects to a centralized mock store. +// Closing GlobalStore instance is required to release RPC client resources. +type GlobalStore struct { + client *rpc.Client +} + +// NewGlobalStore creates a new instance of GlobalStore. +func NewGlobalStore(client *rpc.Client) *GlobalStore { + return &GlobalStore{ + client: client, + } +} + +// Close closes RPC client. +func (s *GlobalStore) Close() error { + s.client.Close() + return nil +} + +// NewNodeStore returns a new instance of NodeStore that retrieves and stores +// chunk data only for a node with address addr. +func (s *GlobalStore) NewNodeStore(addr common.Address) *mock.NodeStore { + return mock.NewNodeStore(addr, s) +} + +// Get calls a Get method to RPC server. +func (s *GlobalStore) Get(addr common.Address, key []byte) (data []byte, err error) { + err = s.client.Call(&data, "mockStore_get", addr, key) + if err != nil && err.Error() == "not found" { + // pass the mock package value of error instead an rpc error + return data, mock.ErrNotFound + } + return data, err +} + +// Put calls a Put method to RPC server. +func (s *GlobalStore) Put(addr common.Address, key []byte, data []byte) error { + err := s.client.Call(nil, "mockStore_put", addr, key, data) + return err +} + +// HasKey calls a HasKey method to RPC server. +func (s *GlobalStore) HasKey(addr common.Address, key []byte) bool { + var has bool + if err := s.client.Call(&has, "mockStore_hasKey", addr, key); err != nil { + log.Error(fmt.Sprintf("mock store HasKey: addr %s, key %064x: %v", addr, key, err)) + return false + } + return has +} diff --git a/swarm/storage/mock/rpc/rpc_test.go b/swarm/storage/mock/rpc/rpc_test.go new file mode 100644 index 000000000..52b634a44 --- /dev/null +++ b/swarm/storage/mock/rpc/rpc_test.go @@ -0,0 +1,41 @@ +// Copyright 2018 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 rpc + +import ( + "testing" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/swarm/storage/mock/mem" + "github.com/ethereum/go-ethereum/swarm/storage/mock/test" +) + +// TestDBStore is running test for a GlobalStore +// using test.MockStore function. +func TestRPCStore(t *testing.T) { + serverStore := mem.NewGlobalStore() + + server := rpc.NewServer() + if err := server.RegisterName("mockStore", serverStore); err != nil { + t.Fatal(err) + } + + store := NewGlobalStore(rpc.DialInProc(server)) + defer store.Close() + + test.MockStore(t, store, 100) +} diff --git a/swarm/storage/mock/test/test.go b/swarm/storage/mock/test/test.go new file mode 100644 index 000000000..02da3af55 --- /dev/null +++ b/swarm/storage/mock/test/test.go @@ -0,0 +1,186 @@ +// Copyright 2018 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 test provides functions that are used for testing +// GlobalStorer implementations. +package test + +import ( + "bytes" + "fmt" + "io" + "strconv" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/swarm/storage" + "github.com/ethereum/go-ethereum/swarm/storage/mock" +) + +// MockStore creates NodeStore instances from provided GlobalStorer, +// each one with a unique address, stores different chunks on them +// and checks if they are retrievable or not on all nodes. +// Attribute n defines the number of NodeStores that will be created. +func MockStore(t *testing.T, globalStore mock.GlobalStorer, n int) { + t.Run("GlobalStore", func(t *testing.T) { + addrs := make([]common.Address, n) + for i := 0; i < n; i++ { + addrs[i] = common.HexToAddress(strconv.FormatInt(int64(i)+1, 16)) + } + + for i, addr := range addrs { + chunkAddr := storage.Address(append(addr[:], []byte(strconv.FormatInt(int64(i)+1, 16))...)) + data := []byte(strconv.FormatInt(int64(i)+1, 16)) + data = append(data, make([]byte, 4096-len(data))...) + globalStore.Put(addr, chunkAddr, data) + + for _, cAddr := range addrs { + cData, err := globalStore.Get(cAddr, chunkAddr) + if cAddr == addr { + if err != nil { + t.Fatalf("get data from store %s key %s: %v", cAddr.Hex(), chunkAddr.Hex(), err) + } + if !bytes.Equal(data, cData) { + t.Fatalf("data on store %s: expected %x, got %x", cAddr.Hex(), data, cData) + } + if !globalStore.HasKey(cAddr, chunkAddr) { + t.Fatalf("expected key %s on global store for node %s, but it was not found", chunkAddr.Hex(), cAddr.Hex()) + } + } else { + if err != mock.ErrNotFound { + t.Fatalf("expected error from store %s: %v, got %v", cAddr.Hex(), mock.ErrNotFound, err) + } + if len(cData) > 0 { + t.Fatalf("data on store %s: expected nil, got %x", cAddr.Hex(), cData) + } + if globalStore.HasKey(cAddr, chunkAddr) { + t.Fatalf("not expected key %s on global store for node %s, but it was found", chunkAddr.Hex(), cAddr.Hex()) + } + } + } + } + }) + + t.Run("NodeStore", func(t *testing.T) { + nodes := make(map[common.Address]*mock.NodeStore) + for i := 0; i < n; i++ { + addr := common.HexToAddress(strconv.FormatInt(int64(i)+1, 16)) + nodes[addr] = globalStore.NewNodeStore(addr) + } + + i := 0 + for addr, store := range nodes { + i++ + chunkAddr := storage.Address(append(addr[:], []byte(fmt.Sprintf("%x", i))...)) + data := []byte(strconv.FormatInt(int64(i)+1, 16)) + data = append(data, make([]byte, 4096-len(data))...) + store.Put(chunkAddr, data) + + for cAddr, cStore := range nodes { + cData, err := cStore.Get(chunkAddr) + if cAddr == addr { + if err != nil { + t.Fatalf("get data from store %s key %s: %v", cAddr.Hex(), chunkAddr.Hex(), err) + } + if !bytes.Equal(data, cData) { + t.Fatalf("data on store %s: expected %x, got %x", cAddr.Hex(), data, cData) + } + if !globalStore.HasKey(cAddr, chunkAddr) { + t.Fatalf("expected key %s on global store for node %s, but it was not found", chunkAddr.Hex(), cAddr.Hex()) + } + } else { + if err != mock.ErrNotFound { + t.Fatalf("expected error from store %s: %v, got %v", cAddr.Hex(), mock.ErrNotFound, err) + } + if len(cData) > 0 { + t.Fatalf("data on store %s: expected nil, got %x", cAddr.Hex(), cData) + } + if globalStore.HasKey(cAddr, chunkAddr) { + t.Fatalf("not expected key %s on global store for node %s, but it was found", chunkAddr.Hex(), cAddr.Hex()) + } + } + } + } + }) +} + +// ImportExport saves chunks to the outStore, exports them to the tar archive, +// imports tar archive to the inStore and checks if all chunks are imported correctly. +func ImportExport(t *testing.T, outStore, inStore mock.GlobalStorer, n int) { + exporter, ok := outStore.(mock.Exporter) + if !ok { + t.Fatal("outStore does not implement mock.Exporter") + } + importer, ok := inStore.(mock.Importer) + if !ok { + t.Fatal("inStore does not implement mock.Importer") + } + addrs := make([]common.Address, n) + for i := 0; i < n; i++ { + addrs[i] = common.HexToAddress(strconv.FormatInt(int64(i)+1, 16)) + } + + for i, addr := range addrs { + chunkAddr := storage.Address(append(addr[:], []byte(strconv.FormatInt(int64(i)+1, 16))...)) + data := []byte(strconv.FormatInt(int64(i)+1, 16)) + data = append(data, make([]byte, 4096-len(data))...) + outStore.Put(addr, chunkAddr, data) + } + + r, w := io.Pipe() + defer r.Close() + + go func() { + defer w.Close() + if _, err := exporter.Export(w); err != nil { + t.Fatalf("export: %v", err) + } + }() + + if _, err := importer.Import(r); err != nil { + t.Fatalf("import: %v", err) + } + + for i, addr := range addrs { + chunkAddr := storage.Address(append(addr[:], []byte(strconv.FormatInt(int64(i)+1, 16))...)) + data := []byte(strconv.FormatInt(int64(i)+1, 16)) + data = append(data, make([]byte, 4096-len(data))...) + for _, cAddr := range addrs { + cData, err := inStore.Get(cAddr, chunkAddr) + if cAddr == addr { + if err != nil { + t.Fatalf("get data from store %s key %s: %v", cAddr.Hex(), chunkAddr.Hex(), err) + } + if !bytes.Equal(data, cData) { + t.Fatalf("data on store %s: expected %x, got %x", cAddr.Hex(), data, cData) + } + if !inStore.HasKey(cAddr, chunkAddr) { + t.Fatalf("expected key %s on global store for node %s, but it was not found", chunkAddr.Hex(), cAddr.Hex()) + } + } else { + if err != mock.ErrNotFound { + t.Fatalf("expected error from store %s: %v, got %v", cAddr.Hex(), mock.ErrNotFound, err) + } + if len(cData) > 0 { + t.Fatalf("data on store %s: expected nil, got %x", cAddr.Hex(), cData) + } + if inStore.HasKey(cAddr, chunkAddr) { + t.Fatalf("not expected key %s on global store for node %s, but it was found", chunkAddr.Hex(), cAddr.Hex()) + } + } + } + } +} |