mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 01:13:45 +00:00
swarm/network: first attempt retrieval functional test using simulation test
This commit is contained in:
parent
b469552371
commit
7427eab9c5
4 changed files with 388 additions and 6 deletions
|
|
@ -17,9 +17,29 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/protocols"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
|
|
@ -200,3 +220,364 @@ func TestStreamerUpstreamRetrieveRequestMsgExchange(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// serviceName is used with the exec adapter so the exec'd binary knows which
|
||||
// service to execute
|
||||
const serviceName = "delivery"
|
||||
|
||||
var services = adapters.Services{
|
||||
serviceName: newService,
|
||||
}
|
||||
|
||||
var (
|
||||
adapter = flag.String("adapter", "sim", "type of simulation: sim|socket|exec|docker")
|
||||
loglevel = flag.Int("loglevel", 5, "verbosity of logs")
|
||||
)
|
||||
|
||||
type roundRobinStore struct {
|
||||
index uint32
|
||||
stores []storage.ChunkStore
|
||||
}
|
||||
|
||||
func newRoundRobinStore(stores ...storage.ChunkStore) *roundRobinStore {
|
||||
return &roundRobinStore{
|
||||
stores: stores,
|
||||
}
|
||||
}
|
||||
|
||||
func (rrs *roundRobinStore) Get(key storage.Key) (*storage.Chunk, error) {
|
||||
return nil, errors.New("get not well defined on round robin store")
|
||||
}
|
||||
|
||||
func (rrs *roundRobinStore) Put(chunk *storage.Chunk) {
|
||||
i := atomic.AddUint32(&rrs.index, 1)
|
||||
idx := int(i) % len(rrs.stores)
|
||||
log.Trace(fmt.Sprintf("put %v into localstore %v", chunk.Key, idx))
|
||||
rrs.stores[idx].Put(chunk)
|
||||
}
|
||||
|
||||
func (rrs *roundRobinStore) Close() {
|
||||
for _, store := range rrs.stores {
|
||||
store.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
// register the Delivery service which will run as a devp2p
|
||||
// protocol when using the exec adapter
|
||||
adapters.RegisterServices(services)
|
||||
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||
}
|
||||
|
||||
func testSimulation(t *testing.T, simf func(adapters.NodeAdapter) (*simulations.StepResult, error)) {
|
||||
var err error
|
||||
var result *simulations.StepResult
|
||||
startedAt := time.Now()
|
||||
|
||||
switch *adapter {
|
||||
case "sim":
|
||||
t.Logf("simadapter")
|
||||
result, err = simf(adapters.NewSimAdapter(services))
|
||||
case "socket":
|
||||
result, err = simf(adapters.NewSocketAdapter(services))
|
||||
case "exec":
|
||||
baseDir, err0 := ioutil.TempDir("", "swarm-test")
|
||||
if err0 != nil {
|
||||
t.Fatal(err0)
|
||||
}
|
||||
defer os.RemoveAll(baseDir)
|
||||
result, err = simf(adapters.NewExecAdapter(baseDir))
|
||||
case "docker":
|
||||
adapter, err0 := adapters.NewDockerAdapter()
|
||||
if err0 != nil {
|
||||
t.Fatal(err0)
|
||||
}
|
||||
result, err = simf(adapter)
|
||||
default:
|
||||
t.Fatal("adapter needs to be one of sim, socket, exec, docker")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Simulation with %d nodes passed in %s", len(result.Passes), result.FinishedAt.Sub(result.StartedAt))
|
||||
var min, max time.Duration
|
||||
var sum int
|
||||
for _, pass := range result.Passes {
|
||||
duration := pass.Sub(result.StartedAt)
|
||||
if sum == 0 || duration < min {
|
||||
min = duration
|
||||
}
|
||||
if duration > max {
|
||||
max = duration
|
||||
}
|
||||
sum += int(duration.Nanoseconds())
|
||||
}
|
||||
t.Logf("Min: %s, Max: %s, Average: %s", min, max, time.Duration(sum/len(result.Passes))*time.Nanosecond)
|
||||
finishedAt := time.Now()
|
||||
t.Logf("Setup: %s, shutdown: %s", result.StartedAt.Sub(startedAt), finishedAt.Sub(result.FinishedAt))
|
||||
}
|
||||
|
||||
func TestDeliveryFromNodes(t *testing.T) {
|
||||
testSimulation(t, testDeliveryFromNodes)
|
||||
}
|
||||
|
||||
var (
|
||||
delivery *Delivery
|
||||
localStores []storage.ChunkStore
|
||||
fileHash storage.Key
|
||||
nodeCount int
|
||||
)
|
||||
|
||||
func setLocalStores(n int) (func(), error) {
|
||||
var datadirs []string
|
||||
localStores = make([]storage.ChunkStore, n)
|
||||
var err error
|
||||
for i := 0; i < n; i++ {
|
||||
// TODO: remove temp datadir after test
|
||||
var datadir string
|
||||
datadir, err = ioutil.TempDir("", "streamer")
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
var localStore *storage.LocalStore
|
||||
localStore, err = storage.NewTestLocalStore(datadir)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
datadirs = append(datadirs, datadir)
|
||||
localStores[i] = localStore
|
||||
}
|
||||
teardown := func() {
|
||||
for _, datadir := range datadirs {
|
||||
os.RemoveAll(datadir)
|
||||
}
|
||||
}
|
||||
return teardown, err
|
||||
}
|
||||
|
||||
func mustReadAll(dpa *storage.DPA, hash storage.Key) (int, error) {
|
||||
r := dpa.Retrieve(fileHash)
|
||||
buf := make([]byte, 1024)
|
||||
var n, total int
|
||||
var err error
|
||||
for (total == 0 || n > 0) && err == nil {
|
||||
log.Warn(fmt.Sprintf("reading %v bytes at offset %v", len(buf), total))
|
||||
n, err = r.ReadAt(buf, int64(total))
|
||||
total += n
|
||||
}
|
||||
log.Warn(fmt.Sprintf("read %v bytes at offset %v", len(buf), total))
|
||||
if err != nil && err != io.EOF {
|
||||
return total, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func testDeliveryFromNodes(adapter adapters.NodeAdapter) (*simulations.StepResult, error) {
|
||||
nodes := 2
|
||||
conns := 0
|
||||
size := 8100
|
||||
skipCheck := true
|
||||
|
||||
trigger := func(net *simulations.Network) chan discover.NodeID {
|
||||
triggerC := make(chan discover.NodeID)
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
go func() {
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
triggerC <- net.Nodes[0].ID()
|
||||
}
|
||||
}()
|
||||
return triggerC
|
||||
}
|
||||
|
||||
action := func(net *simulations.Network) func(context.Context) error {
|
||||
rrdpa := storage.NewDPA(newRoundRobinStore(localStores[1:]...), storage.NewChunkerParams())
|
||||
rrdpa.Start()
|
||||
dpacs := storage.NewDpaChunkStore(localStores[0].(*storage.LocalStore), func(chunk *storage.Chunk) error { return delivery.RequestFromPeers(chunk.Key[:], skipCheck) })
|
||||
dpa := storage.NewDPA(dpacs, storage.NewChunkerParams())
|
||||
dpa.Start()
|
||||
return func(context.Context) error {
|
||||
defer rrdpa.Stop()
|
||||
hash, wait, err := rrdpa.Store(crand.Reader, int64(size))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wait()
|
||||
fileHash = hash
|
||||
go func() {
|
||||
defer dpa.Stop()
|
||||
log.Debug(fmt.Sprintf("retrieve %v", fileHash))
|
||||
n, err := mustReadAll(dpa, fileHash)
|
||||
log.Debug(fmt.Sprintf("retrieved %v", fileHash), "read", n, "err", err)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
check := func(net *simulations.Network, dpa *storage.DPA) func(ctx context.Context, id discover.NodeID) (bool, error) {
|
||||
return func(ctx context.Context, id discover.NodeID) (bool, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, ctx.Err()
|
||||
default:
|
||||
}
|
||||
log.Warn(fmt.Sprintf("try to locally retrieve %v", fileHash))
|
||||
total, err := mustReadAll(dpa, fileHash)
|
||||
if err != nil || total != size {
|
||||
log.Warn(fmt.Sprintf("number of bytes read %v/%v (error: %v)", total, size, err))
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
// node := net.GetNode(id)
|
||||
// if node == nil {
|
||||
// return false, fmt.Errorf("unknown node: %s", id)
|
||||
// }
|
||||
// client, err := node.Client()
|
||||
// if err != nil {
|
||||
// return false, fmt.Errorf("error getting node client: %s", err)
|
||||
// }
|
||||
// var response int
|
||||
// if err := client.Call(&response, "test_haslocal", hash); err != nil {
|
||||
// return false, fmt.Errorf("error getting bzz_has response: %s", err)
|
||||
// }
|
||||
// log.Debug(fmt.Sprintf("node has: %v\n%v", id, response))
|
||||
// return response == 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
result, err := runSimulation(nodes, conns, action, trigger, check, adapter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Setting up simulation failed: %v", err)
|
||||
}
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("Simulation failed: %s", result.Error)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func runSimulation(nodes, conns int, action func(*simulations.Network) func(context.Context) error, trigger func(*simulations.Network) chan discover.NodeID, check func(*simulations.Network, *storage.DPA) func(context.Context, discover.NodeID) (bool, error), adapter adapters.NodeAdapter) (*simulations.StepResult, error) {
|
||||
// create network
|
||||
net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{
|
||||
ID: "0",
|
||||
DefaultService: serviceName,
|
||||
})
|
||||
defer net.Shutdown()
|
||||
teardown, err := setLocalStores(nodes)
|
||||
defer teardown()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make([]discover.NodeID, nodes)
|
||||
nodeCount = 0
|
||||
for i := 0; i < nodes; i++ {
|
||||
node, err := net.NewNode()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error starting node: %s", err)
|
||||
}
|
||||
if err := net.Start(node.ID()); err != nil {
|
||||
return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err)
|
||||
}
|
||||
ids[i] = node.ID()
|
||||
}
|
||||
|
||||
// run a simulation which connects the 10 nodes in a ring and waits
|
||||
// for full peer discovery
|
||||
var addrs [][]byte
|
||||
wg := sync.WaitGroup{}
|
||||
for i := range ids {
|
||||
// collect the overlay addresses, to
|
||||
addrs = append(addrs, ToOverlayAddr(ids[i].Bytes()))
|
||||
for j := 0; j < conns; j++ {
|
||||
var k int
|
||||
if j == 0 {
|
||||
k = i - 1
|
||||
} else {
|
||||
k = rand.Intn(len(ids))
|
||||
}
|
||||
if i > 0 {
|
||||
wg.Add(1)
|
||||
go func(i, k int) {
|
||||
defer wg.Done()
|
||||
net.Connect(ids[i], ids[k])
|
||||
}(i, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
log.Debug(fmt.Sprintf("nodes: %v", len(addrs)))
|
||||
|
||||
// 64 nodes ~ 1min
|
||||
// 128 nodes ~
|
||||
dpa := storage.NewDPA(localStores[0], storage.NewChunkerParams())
|
||||
dpa.Start()
|
||||
timeout := 300 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{
|
||||
Action: action(net),
|
||||
Trigger: trigger(net),
|
||||
Expect: &simulations.Expectation{
|
||||
Nodes: ids,
|
||||
Check: check(net, dpa),
|
||||
},
|
||||
})
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func newService(ctx *adapters.ServiceContext) (node.Service, error) {
|
||||
id := ctx.Config.ID
|
||||
addr := NewAddrFromNodeID(id)
|
||||
kad := NewKademlia(addr.Over(), NewKadParams())
|
||||
localStore := localStores[nodeCount]
|
||||
dbAccess := NewDbAccess(localStore.(*storage.LocalStore))
|
||||
streamer := NewStreamer(NewDelivery(kad, dbAccess))
|
||||
if nodeCount == 0 {
|
||||
delivery = streamer.delivery
|
||||
}
|
||||
nodeCount++
|
||||
run := func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
bzzPeer := &bzzPeer{
|
||||
Peer: protocols.NewPeer(p, rw, StreamerSpec),
|
||||
localAddr: addr,
|
||||
BzzAddr: NewAddrFromNodeID(p.ID()),
|
||||
}
|
||||
kad.On(bzzPeer)
|
||||
streamer.Subscribe(p.ID(), retrieveRequestStream, nil, 0, 0, Top, true)
|
||||
return streamer.Run(bzzPeer)
|
||||
}
|
||||
|
||||
return &testDeliveryService{
|
||||
run: run,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type testDeliveryService struct {
|
||||
run func(p *p2p.Peer, rw p2p.MsgReadWriter) error
|
||||
}
|
||||
|
||||
func (tds *testDeliveryService) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{
|
||||
{
|
||||
Name: StreamerSpec.Name,
|
||||
Version: StreamerSpec.Version,
|
||||
Length: StreamerSpec.Length(),
|
||||
Run: tds.run,
|
||||
// NodeInfo: ,
|
||||
// PeerInfo: ,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (b *testDeliveryService) APIs() []rpc.API {
|
||||
return []rpc.API{}
|
||||
}
|
||||
|
||||
func (b *testDeliveryService) Start(server *p2p.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testDeliveryService) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,16 +25,16 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/protocols"
|
||||
p2ptest "github.com/ethereum/go-ethereum/p2p/testing"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlWarn, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||
}
|
||||
//
|
||||
// func init() {
|
||||
// log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.LvlWarn, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||
// }
|
||||
|
||||
// TODO: extract newStreamer
|
||||
func newStreamerTester(t *testing.T) (*p2ptest.ProtocolTester, *Streamer, *storage.LocalStore, func(), error) {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ func NewLocalDPA(datadir string, basekey []byte) (*DPA, error) {
|
|||
}
|
||||
|
||||
func NewDPA(store ChunkStore, params *ChunkerParams) *DPA {
|
||||
chunker := NewTreeChunker(params)
|
||||
chunker := NewPyramidChunker(params)
|
||||
return &DPA{
|
||||
Chunker: chunker,
|
||||
ChunkStore: store,
|
||||
|
|
|
|||
|
|
@ -136,10 +136,11 @@ func NewPyramidChunker(params *ChunkerParams) (self *PyramidChunker) {
|
|||
return
|
||||
}
|
||||
|
||||
func (self *PyramidChunker) Join(key Key, chunkC chan *Chunk) LazySectionReader {
|
||||
func (self *PyramidChunker) Join(key Key, chunkC chan *Chunk, depth int) LazySectionReader {
|
||||
return &LazyChunkReader{
|
||||
key: key,
|
||||
chunkC: chunkC,
|
||||
depth: depth,
|
||||
chunkSize: self.chunkSize,
|
||||
branches: self.branches,
|
||||
hashSize: self.hashSize,
|
||||
|
|
|
|||
Loading…
Reference in a new issue