|
|
@ -18,12 +18,8 @@ package stream |
|
|
|
import ( |
|
|
|
import ( |
|
|
|
"context" |
|
|
|
"context" |
|
|
|
crand "crypto/rand" |
|
|
|
crand "crypto/rand" |
|
|
|
"encoding/json" |
|
|
|
|
|
|
|
"flag" |
|
|
|
|
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
"io" |
|
|
|
"io" |
|
|
|
"io/ioutil" |
|
|
|
|
|
|
|
"math/rand" |
|
|
|
|
|
|
|
"os" |
|
|
|
"os" |
|
|
|
"sync" |
|
|
|
"sync" |
|
|
|
"testing" |
|
|
|
"testing" |
|
|
@ -31,82 +27,27 @@ import ( |
|
|
|
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
|
|
"github.com/ethereum/go-ethereum/common" |
|
|
|
"github.com/ethereum/go-ethereum/log" |
|
|
|
"github.com/ethereum/go-ethereum/log" |
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/node" |
|
|
|
"github.com/ethereum/go-ethereum/p2p" |
|
|
|
"github.com/ethereum/go-ethereum/p2p" |
|
|
|
"github.com/ethereum/go-ethereum/p2p/discover" |
|
|
|
"github.com/ethereum/go-ethereum/p2p/discover" |
|
|
|
"github.com/ethereum/go-ethereum/p2p/simulations" |
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" |
|
|
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters" |
|
|
|
"github.com/ethereum/go-ethereum/rpc" |
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/swarm/network" |
|
|
|
"github.com/ethereum/go-ethereum/swarm/network" |
|
|
|
streamTesting "github.com/ethereum/go-ethereum/swarm/network/stream/testing" |
|
|
|
"github.com/ethereum/go-ethereum/swarm/network/simulation" |
|
|
|
"github.com/ethereum/go-ethereum/swarm/pot" |
|
|
|
"github.com/ethereum/go-ethereum/swarm/pot" |
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/swarm/state" |
|
|
|
"github.com/ethereum/go-ethereum/swarm/storage" |
|
|
|
"github.com/ethereum/go-ethereum/swarm/storage" |
|
|
|
|
|
|
|
mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
const testMinProxBinSize = 2 |
|
|
|
const testMinProxBinSize = 2 |
|
|
|
const MaxTimeout = 600 |
|
|
|
const MaxTimeout = 600 |
|
|
|
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
|
|
|
pof = pot.DefaultPof(256) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
conf *synctestConfig |
|
|
|
|
|
|
|
ids []discover.NodeID |
|
|
|
|
|
|
|
datadirs map[discover.NodeID]string |
|
|
|
|
|
|
|
ppmap map[string]*network.PeerPot |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
live bool |
|
|
|
|
|
|
|
history bool |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
longrunning = flag.Bool("longrunning", false, "do run long-running tests") |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type synctestConfig struct { |
|
|
|
type synctestConfig struct { |
|
|
|
addrs [][]byte |
|
|
|
addrs [][]byte |
|
|
|
hashes []storage.Address |
|
|
|
hashes []storage.Address |
|
|
|
idToChunksMap map[discover.NodeID][]int |
|
|
|
idToChunksMap map[discover.NodeID][]int |
|
|
|
chunksToNodesMap map[string][]int |
|
|
|
chunksToNodesMap map[string][]int |
|
|
|
addrToIdMap map[string]discover.NodeID |
|
|
|
addrToIDMap map[string]discover.NodeID |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func init() { |
|
|
|
|
|
|
|
rand.Seed(time.Now().Unix()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//common_test needs to initialize the test in a init() func
|
|
|
|
|
|
|
|
//in order for adapters to register the NewStreamerService;
|
|
|
|
|
|
|
|
//this service is dependent on some global variables
|
|
|
|
|
|
|
|
//we thus need to initialize first as init() as well.
|
|
|
|
|
|
|
|
func initSyncTest() { |
|
|
|
|
|
|
|
//assign the toAddr func so NewStreamerService can build the addr
|
|
|
|
|
|
|
|
toAddr = func(id discover.NodeID) *network.BzzAddr { |
|
|
|
|
|
|
|
addr := network.NewAddrFromNodeID(id) |
|
|
|
|
|
|
|
return addr |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//global func to create local store
|
|
|
|
|
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
createStoreFunc = createMockStore |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
createStoreFunc = createTestLocalStorageForId |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//local stores
|
|
|
|
|
|
|
|
stores = make(map[discover.NodeID]storage.ChunkStore) |
|
|
|
|
|
|
|
//data directories for each node and store
|
|
|
|
|
|
|
|
datadirs = make(map[discover.NodeID]string) |
|
|
|
|
|
|
|
//deliveries for each node
|
|
|
|
|
|
|
|
deliveries = make(map[discover.NodeID]*Delivery) |
|
|
|
|
|
|
|
//registries, map of discover.NodeID to its streamer
|
|
|
|
|
|
|
|
registries = make(map[discover.NodeID]*TestRegistry) |
|
|
|
|
|
|
|
//not needed for this test but required from common_test for NewStreamService
|
|
|
|
|
|
|
|
waitPeerErrC = make(chan error) |
|
|
|
|
|
|
|
//also not needed for this test but required for NewStreamService
|
|
|
|
|
|
|
|
peerCount = func(id discover.NodeID) int { |
|
|
|
|
|
|
|
if ids[0] == id || ids[len(ids)-1] == id { |
|
|
|
|
|
|
|
return 1 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return 2 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
createGlobalStore() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//This test is a syncing test for nodes.
|
|
|
|
//This test is a syncing test for nodes.
|
|
|
@ -116,12 +57,12 @@ func initSyncTest() { |
|
|
|
//to the pivot node, and we check that nodes get the chunks
|
|
|
|
//to the pivot node, and we check that nodes get the chunks
|
|
|
|
//they are expected to store based on the syncing protocol.
|
|
|
|
//they are expected to store based on the syncing protocol.
|
|
|
|
//Number of chunks and nodes can be provided via commandline too.
|
|
|
|
//Number of chunks and nodes can be provided via commandline too.
|
|
|
|
func TestSyncing(t *testing.T) { |
|
|
|
func TestSyncingViaGlobalSync(t *testing.T) { |
|
|
|
//if nodes/chunks have been provided via commandline,
|
|
|
|
//if nodes/chunks have been provided via commandline,
|
|
|
|
//run the tests with these values
|
|
|
|
//run the tests with these values
|
|
|
|
if *nodes != 0 && *chunks != 0 { |
|
|
|
if *nodes != 0 && *chunks != 0 { |
|
|
|
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) |
|
|
|
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) |
|
|
|
testSyncing(t, *chunks, *nodes) |
|
|
|
testSyncingViaGlobalSync(t, *chunks, *nodes) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
var nodeCnt []int |
|
|
|
var nodeCnt []int |
|
|
|
var chnkCnt []int |
|
|
|
var chnkCnt []int |
|
|
@ -138,230 +79,279 @@ func TestSyncing(t *testing.T) { |
|
|
|
for _, chnk := range chnkCnt { |
|
|
|
for _, chnk := range chnkCnt { |
|
|
|
for _, n := range nodeCnt { |
|
|
|
for _, n := range nodeCnt { |
|
|
|
log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) |
|
|
|
log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) |
|
|
|
testSyncing(t, chnk, n) |
|
|
|
testSyncingViaGlobalSync(t, chnk, n) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//Do run the tests
|
|
|
|
func TestSyncingViaDirectSubscribe(t *testing.T) { |
|
|
|
//Every test runs 3 times, a live, a history, and a live AND history
|
|
|
|
//if nodes/chunks have been provided via commandline,
|
|
|
|
func testSyncing(t *testing.T, chunkCount int, nodeCount int) { |
|
|
|
//run the tests with these values
|
|
|
|
//test live and NO history
|
|
|
|
if *nodes != 0 && *chunks != 0 { |
|
|
|
log.Info("Testing live and no history") |
|
|
|
log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) |
|
|
|
live = true |
|
|
|
err := testSyncingViaDirectSubscribe(*chunks, *nodes) |
|
|
|
history = false |
|
|
|
if err != nil { |
|
|
|
err := runSyncTest(chunkCount, nodeCount, live, history) |
|
|
|
t.Fatal(err) |
|
|
|
if err != nil { |
|
|
|
} |
|
|
|
t.Fatal(err) |
|
|
|
} else { |
|
|
|
} |
|
|
|
var nodeCnt []int |
|
|
|
//test history only
|
|
|
|
var chnkCnt []int |
|
|
|
log.Info("Testing history only") |
|
|
|
//if the `longrunning` flag has been provided
|
|
|
|
live = false |
|
|
|
//run more test combinations
|
|
|
|
history = true |
|
|
|
if *longrunning { |
|
|
|
err = runSyncTest(chunkCount, nodeCount, live, history) |
|
|
|
chnkCnt = []int{1, 8, 32, 256, 1024} |
|
|
|
if err != nil { |
|
|
|
nodeCnt = []int{32, 16} |
|
|
|
t.Fatal(err) |
|
|
|
} else { |
|
|
|
} |
|
|
|
//default test
|
|
|
|
//finally test live and history
|
|
|
|
chnkCnt = []int{4, 32} |
|
|
|
log.Info("Testing live and history") |
|
|
|
nodeCnt = []int{32, 16} |
|
|
|
live = true |
|
|
|
} |
|
|
|
err = runSyncTest(chunkCount, nodeCount, live, history) |
|
|
|
for _, chnk := range chnkCnt { |
|
|
|
if err != nil { |
|
|
|
for _, n := range nodeCnt { |
|
|
|
t.Fatal(err) |
|
|
|
log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) |
|
|
|
|
|
|
|
err := testSyncingViaDirectSubscribe(chnk, n) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Fatal(err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) { |
|
|
|
The test generates the given number of chunks |
|
|
|
sim := simulation.New(map[string]simulation.ServiceFunc{ |
|
|
|
|
|
|
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { |
|
|
|
|
|
|
|
|
|
|
|
The upload is done by dependency to the global |
|
|
|
id := ctx.Config.ID |
|
|
|
`live` and `history` variables; |
|
|
|
addr := network.NewAddrFromNodeID(id) |
|
|
|
|
|
|
|
store, datadir, err := createTestLocalStorageForID(id, addr) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bucket.Store(bucketKeyStore, store) |
|
|
|
|
|
|
|
cleanup = func() { |
|
|
|
|
|
|
|
os.RemoveAll(datadir) |
|
|
|
|
|
|
|
store.Close() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
localStore := store.(*storage.LocalStore) |
|
|
|
|
|
|
|
db := storage.NewDBAPI(localStore) |
|
|
|
|
|
|
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams()) |
|
|
|
|
|
|
|
delivery := NewDelivery(kad, db) |
|
|
|
|
|
|
|
|
|
|
|
If `live` is set, first stream subscriptions are established, then |
|
|
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{ |
|
|
|
upload to a random node. |
|
|
|
DoSync: true, |
|
|
|
|
|
|
|
SyncUpdateDelay: 3 * time.Second, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
bucket.Store(bucketKeyRegistry, r) |
|
|
|
|
|
|
|
|
|
|
|
If `history` is enabled, first upload then build up subscriptions. |
|
|
|
return r, cleanup, nil |
|
|
|
|
|
|
|
|
|
|
|
For every chunk generated, the nearest node addresses |
|
|
|
}, |
|
|
|
are identified, we verify that the nodes closer to the |
|
|
|
}) |
|
|
|
chunk addresses actually do have the chunks in their local stores. |
|
|
|
defer sim.Close() |
|
|
|
|
|
|
|
|
|
|
|
The test loads a snapshot file to construct the swarm network, |
|
|
|
log.Info("Initializing test config") |
|
|
|
assuming that the snapshot file identifies a healthy |
|
|
|
|
|
|
|
kademlia network. The snapshot should have 'streamer' in its service list. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
For every test run, a series of three tests will be executed: |
|
|
|
conf := &synctestConfig{} |
|
|
|
- a LIVE test first, where first subscriptions are established, |
|
|
|
|
|
|
|
then a file (random chunks) is uploaded |
|
|
|
|
|
|
|
- a HISTORY test, where the file is uploaded first, and then |
|
|
|
|
|
|
|
the subscriptions are established |
|
|
|
|
|
|
|
- a crude LIVE AND HISTORY test last, where (different) chunks |
|
|
|
|
|
|
|
are uploaded twice, once before and once after subscriptions |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error { |
|
|
|
|
|
|
|
initSyncTest() |
|
|
|
|
|
|
|
//the ids of the snapshot nodes, initiate only now as we need nodeCount
|
|
|
|
|
|
|
|
ids = make([]discover.NodeID, nodeCount) |
|
|
|
|
|
|
|
//initialize the test struct
|
|
|
|
|
|
|
|
conf = &synctestConfig{} |
|
|
|
|
|
|
|
//map of discover ID to indexes of chunks expected at that ID
|
|
|
|
//map of discover ID to indexes of chunks expected at that ID
|
|
|
|
conf.idToChunksMap = make(map[discover.NodeID][]int) |
|
|
|
conf.idToChunksMap = make(map[discover.NodeID][]int) |
|
|
|
//map of overlay address to discover ID
|
|
|
|
//map of overlay address to discover ID
|
|
|
|
conf.addrToIdMap = make(map[string]discover.NodeID) |
|
|
|
conf.addrToIDMap = make(map[string]discover.NodeID) |
|
|
|
//array where the generated chunk hashes will be stored
|
|
|
|
//array where the generated chunk hashes will be stored
|
|
|
|
conf.hashes = make([]storage.Address, 0) |
|
|
|
conf.hashes = make([]storage.Address, 0) |
|
|
|
//channel to trigger node checks in the simulation
|
|
|
|
|
|
|
|
trigger := make(chan discover.NodeID) |
|
|
|
err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) |
|
|
|
//channel to check for disconnection errors
|
|
|
|
|
|
|
|
disconnectC := make(chan error) |
|
|
|
|
|
|
|
//channel to close disconnection watcher routine
|
|
|
|
|
|
|
|
quitC := make(chan struct{}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//load nodes from the snapshot file
|
|
|
|
|
|
|
|
net, err := initNetWithSnapshot(nodeCount) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
t.Fatal(err) |
|
|
|
} |
|
|
|
} |
|
|
|
var rpcSubscriptionsWg sync.WaitGroup |
|
|
|
|
|
|
|
//do cleanup after test is terminated
|
|
|
|
|
|
|
|
defer func() { |
|
|
|
|
|
|
|
// close quitC channel to signall all goroutines to clanup
|
|
|
|
|
|
|
|
// before calling simulation network shutdown.
|
|
|
|
|
|
|
|
close(quitC) |
|
|
|
|
|
|
|
//wait for all rpc subscriptions to unsubscribe
|
|
|
|
|
|
|
|
rpcSubscriptionsWg.Wait() |
|
|
|
|
|
|
|
//shutdown the snapshot network
|
|
|
|
|
|
|
|
net.Shutdown() |
|
|
|
|
|
|
|
//after the test, clean up local stores initialized with createLocalStoreForId
|
|
|
|
|
|
|
|
localStoreCleanup() |
|
|
|
|
|
|
|
//finally clear all data directories
|
|
|
|
|
|
|
|
datadirsCleanup() |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
//get the nodes of the network
|
|
|
|
|
|
|
|
nodes := net.GetNodes() |
|
|
|
|
|
|
|
//select one index at random...
|
|
|
|
|
|
|
|
idx := rand.Intn(len(nodes)) |
|
|
|
|
|
|
|
//...and get the the node at that index
|
|
|
|
|
|
|
|
//this is the node selected for upload
|
|
|
|
|
|
|
|
node := nodes[idx] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.Info("Initializing test config") |
|
|
|
ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute) |
|
|
|
//iterate over all nodes...
|
|
|
|
defer cancelSimRun() |
|
|
|
for c := 0; c < len(nodes); c++ { |
|
|
|
|
|
|
|
//create an array of discovery node IDs
|
|
|
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { |
|
|
|
ids[c] = nodes[c].ID() |
|
|
|
nodeIDs := sim.UpNodeIDs() |
|
|
|
//get the kademlia overlay address from this ID
|
|
|
|
for _, n := range nodeIDs { |
|
|
|
a := network.ToOverlayAddr(ids[c].Bytes()) |
|
|
|
//get the kademlia overlay address from this ID
|
|
|
|
//append it to the array of all overlay addresses
|
|
|
|
a := network.ToOverlayAddr(n.Bytes()) |
|
|
|
conf.addrs = append(conf.addrs, a) |
|
|
|
//append it to the array of all overlay addresses
|
|
|
|
//the proximity calculation is on overlay addr,
|
|
|
|
conf.addrs = append(conf.addrs, a) |
|
|
|
//the p2p/simulations check func triggers on discover.NodeID,
|
|
|
|
//the proximity calculation is on overlay addr,
|
|
|
|
//so we need to know which overlay addr maps to which nodeID
|
|
|
|
//the p2p/simulations check func triggers on discover.NodeID,
|
|
|
|
conf.addrToIdMap[string(a)] = ids[c] |
|
|
|
//so we need to know which overlay addr maps to which nodeID
|
|
|
|
} |
|
|
|
conf.addrToIDMap[string(a)] = n |
|
|
|
log.Info("Test config successfully initialized") |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//only needed for healthy call when debugging
|
|
|
|
//get the the node at that index
|
|
|
|
ppmap = network.NewPeerPotMap(testMinProxBinSize, conf.addrs) |
|
|
|
//this is the node selected for upload
|
|
|
|
|
|
|
|
node := sim.RandomUpNode() |
|
|
|
//define the action to be performed before the test checks: start syncing
|
|
|
|
item, ok := sim.NodeItem(node.ID, bucketKeyStore) |
|
|
|
action := func(ctx context.Context) error { |
|
|
|
if !ok { |
|
|
|
//first run the health check on all nodes,
|
|
|
|
return fmt.Errorf("No localstore") |
|
|
|
//wait until nodes are all healthy
|
|
|
|
} |
|
|
|
ticker := time.NewTicker(200 * time.Millisecond) |
|
|
|
lstore := item.(*storage.LocalStore) |
|
|
|
defer ticker.Stop() |
|
|
|
hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore) |
|
|
|
for range ticker.C { |
|
|
|
if err != nil { |
|
|
|
healthy := true |
|
|
|
return err |
|
|
|
for _, id := range ids { |
|
|
|
} |
|
|
|
r := registries[id] |
|
|
|
conf.hashes = append(conf.hashes, hashes...) |
|
|
|
//PeerPot for this node
|
|
|
|
mapKeysToNodes(conf) |
|
|
|
addr := common.Bytes2Hex(network.ToOverlayAddr(id.Bytes())) |
|
|
|
|
|
|
|
pp := ppmap[addr] |
|
|
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil { |
|
|
|
//call Healthy RPC
|
|
|
|
return err |
|
|
|
h := r.delivery.overlay.Healthy(pp) |
|
|
|
|
|
|
|
//print info
|
|
|
|
|
|
|
|
log.Debug(r.delivery.overlay.String()) |
|
|
|
|
|
|
|
log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full)) |
|
|
|
|
|
|
|
if !h.GotNN || !h.Full { |
|
|
|
|
|
|
|
healthy = false |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if healthy { |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if history { |
|
|
|
// File retrieval check is repeated until all uploaded files are retrieved from all nodes
|
|
|
|
log.Info("Uploading for history") |
|
|
|
// or until the timeout is reached.
|
|
|
|
//If testing only history, we upload the chunk(s) first
|
|
|
|
allSuccess := false |
|
|
|
chunks, err := uploadFileToSingleNodeStore(node.ID(), chunkCount) |
|
|
|
var gDir string |
|
|
|
|
|
|
|
var globalStore *mockdb.GlobalStore |
|
|
|
|
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
gDir, globalStore, err = createGlobalStore() |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil") |
|
|
|
} |
|
|
|
} |
|
|
|
conf.hashes = append(conf.hashes, chunks...) |
|
|
|
defer func() { |
|
|
|
//finally map chunks to the closest addresses
|
|
|
|
os.RemoveAll(gDir) |
|
|
|
mapKeysToNodes(conf) |
|
|
|
err := globalStore.Close() |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
log.Error("Error closing global store! %v", "err", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
for !allSuccess { |
|
|
|
|
|
|
|
for _, id := range nodeIDs { |
|
|
|
|
|
|
|
//for each expected chunk, check if it is in the local store
|
|
|
|
|
|
|
|
localChunks := conf.idToChunksMap[id] |
|
|
|
|
|
|
|
localSuccess := true |
|
|
|
|
|
|
|
for _, ch := range localChunks { |
|
|
|
|
|
|
|
//get the real chunk by the index in the index array
|
|
|
|
|
|
|
|
chunk := conf.hashes[ch] |
|
|
|
|
|
|
|
log.Trace(fmt.Sprintf("node has chunk: %s:", chunk)) |
|
|
|
|
|
|
|
//check if the expected chunk is indeed in the localstore
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
//use the globalStore if the mockStore should be used; in that case,
|
|
|
|
|
|
|
|
//the complete localStore stack is bypassed for getting the chunk
|
|
|
|
|
|
|
|
_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
//use the actual localstore
|
|
|
|
|
|
|
|
item, ok := sim.NodeItem(id, bucketKeyStore) |
|
|
|
|
|
|
|
if !ok { |
|
|
|
|
|
|
|
return fmt.Errorf("Error accessing localstore") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
lstore := item.(*storage.LocalStore) |
|
|
|
|
|
|
|
_, err = lstore.Get(ctx, chunk) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id)) |
|
|
|
|
|
|
|
localSuccess = false |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
allSuccess = localSuccess |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if !allSuccess { |
|
|
|
|
|
|
|
return fmt.Errorf("Not all chunks succeeded!") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
//variables needed to wait for all subscriptions established before uploading
|
|
|
|
if result.Error != nil { |
|
|
|
errc := make(chan error) |
|
|
|
t.Fatal(result.Error) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//now setup and start event watching in order to know when we can upload
|
|
|
|
/* |
|
|
|
ctx, watchCancel := context.WithTimeout(context.Background(), MaxTimeout*time.Second) |
|
|
|
The test generates the given number of chunks |
|
|
|
defer watchCancel() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.Info("Setting up stream subscription") |
|
|
|
For every chunk generated, the nearest node addresses |
|
|
|
|
|
|
|
are identified, we verify that the nodes closer to the |
|
|
|
|
|
|
|
chunk addresses actually do have the chunks in their local stores. |
|
|
|
|
|
|
|
|
|
|
|
//We need two iterations, one to subscribe to the subscription events
|
|
|
|
The test loads a snapshot file to construct the swarm network, |
|
|
|
//(so we know when setup phase is finished), and one to
|
|
|
|
assuming that the snapshot file identifies a healthy |
|
|
|
//actually run the stream subscriptions. We can't do it in the same iteration,
|
|
|
|
kademlia network. The snapshot should have 'streamer' in its service list. |
|
|
|
//because while the first nodes in the loop are setting up subscriptions,
|
|
|
|
*/ |
|
|
|
//the latter ones have not subscribed to listen to peer events yet,
|
|
|
|
func testSyncingViaDirectSubscribe(chunkCount int, nodeCount int) error { |
|
|
|
//and then we miss events.
|
|
|
|
sim := simulation.New(map[string]simulation.ServiceFunc{ |
|
|
|
|
|
|
|
"streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { |
|
|
|
|
|
|
|
|
|
|
|
//first iteration: setup disconnection watcher and subscribe to peer events
|
|
|
|
id := ctx.Config.ID |
|
|
|
for j, id := range ids { |
|
|
|
addr := network.NewAddrFromNodeID(id) |
|
|
|
log.Trace(fmt.Sprintf("Subscribe to subscription events: %d", j)) |
|
|
|
store, datadir, err := createTestLocalStorageForID(id, addr) |
|
|
|
client, err := net.GetNode(id).Client() |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return nil, nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
bucket.Store(bucketKeyStore, store) |
|
|
|
wsDoneC := watchSubscriptionEvents(ctx, id, client, errc, quitC) |
|
|
|
cleanup = func() { |
|
|
|
// doneC is nil, the error happened which is sent to errc channel, already
|
|
|
|
os.RemoveAll(datadir) |
|
|
|
if wsDoneC == nil { |
|
|
|
store.Close() |
|
|
|
continue |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
rpcSubscriptionsWg.Add(1) |
|
|
|
localStore := store.(*storage.LocalStore) |
|
|
|
go func() { |
|
|
|
db := storage.NewDBAPI(localStore) |
|
|
|
<-wsDoneC |
|
|
|
kad := network.NewKademlia(addr.Over(), network.NewKadParams()) |
|
|
|
rpcSubscriptionsWg.Done() |
|
|
|
delivery := NewDelivery(kad, db) |
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//watch for peers disconnecting
|
|
|
|
r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), nil) |
|
|
|
wdDoneC, err := streamTesting.WatchDisconnections(id, client, disconnectC, quitC) |
|
|
|
bucket.Store(bucketKeyRegistry, r) |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams()) |
|
|
|
} |
|
|
|
bucket.Store(bucketKeyFileStore, fileStore) |
|
|
|
rpcSubscriptionsWg.Add(1) |
|
|
|
|
|
|
|
go func() { |
|
|
|
return r, cleanup, nil |
|
|
|
<-wdDoneC |
|
|
|
|
|
|
|
rpcSubscriptionsWg.Done() |
|
|
|
}, |
|
|
|
}() |
|
|
|
}) |
|
|
|
|
|
|
|
defer sim.Close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute) |
|
|
|
|
|
|
|
defer cancelSimRun() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
conf := &synctestConfig{} |
|
|
|
|
|
|
|
//map of discover ID to indexes of chunks expected at that ID
|
|
|
|
|
|
|
|
conf.idToChunksMap = make(map[discover.NodeID][]int) |
|
|
|
|
|
|
|
//map of overlay address to discover ID
|
|
|
|
|
|
|
|
conf.addrToIDMap = make(map[string]discover.NodeID) |
|
|
|
|
|
|
|
//array where the generated chunk hashes will be stored
|
|
|
|
|
|
|
|
conf.hashes = make([]storage.Address, 0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { |
|
|
|
|
|
|
|
nodeIDs := sim.UpNodeIDs() |
|
|
|
|
|
|
|
for _, n := range nodeIDs { |
|
|
|
|
|
|
|
//get the kademlia overlay address from this ID
|
|
|
|
|
|
|
|
a := network.ToOverlayAddr(n.Bytes()) |
|
|
|
|
|
|
|
//append it to the array of all overlay addresses
|
|
|
|
|
|
|
|
conf.addrs = append(conf.addrs, a) |
|
|
|
|
|
|
|
//the proximity calculation is on overlay addr,
|
|
|
|
|
|
|
|
//the p2p/simulations check func triggers on discover.NodeID,
|
|
|
|
|
|
|
|
//so we need to know which overlay addr maps to which nodeID
|
|
|
|
|
|
|
|
conf.addrToIDMap[string(a)] = n |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//second iteration: start syncing
|
|
|
|
var subscriptionCount int |
|
|
|
for j, id := range ids { |
|
|
|
|
|
|
|
|
|
|
|
filter := simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("stream").MsgCode(4) |
|
|
|
|
|
|
|
eventC := sim.PeerEvents(ctx, nodeIDs, filter) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for j, node := range nodeIDs { |
|
|
|
log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j)) |
|
|
|
log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j)) |
|
|
|
client, err := net.GetNode(id).Client() |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//start syncing!
|
|
|
|
//start syncing!
|
|
|
|
|
|
|
|
item, ok := sim.NodeItem(node, bucketKeyRegistry) |
|
|
|
|
|
|
|
if !ok { |
|
|
|
|
|
|
|
return fmt.Errorf("No registry") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
registry := item.(*Registry) |
|
|
|
|
|
|
|
|
|
|
|
var cnt int |
|
|
|
var cnt int |
|
|
|
err = client.CallContext(ctx, &cnt, "stream_startSyncing") |
|
|
|
cnt, err = startSyncing(registry, conf) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
@ -370,117 +360,89 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error { |
|
|
|
subscriptionCount += cnt |
|
|
|
subscriptionCount += cnt |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//now wait until the number of expected subscriptions has been finished
|
|
|
|
for e := range eventC { |
|
|
|
//`watchSubscriptionEvents` will write with a `nil` value to errc
|
|
|
|
if e.Error != nil { |
|
|
|
for err := range errc { |
|
|
|
return e.Error |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
//`nil` received, decrement count
|
|
|
|
|
|
|
|
subscriptionCount-- |
|
|
|
subscriptionCount-- |
|
|
|
//all subscriptions received
|
|
|
|
|
|
|
|
if subscriptionCount == 0 { |
|
|
|
if subscriptionCount == 0 { |
|
|
|
break |
|
|
|
break |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//select a random node for upload
|
|
|
|
log.Info("Stream subscriptions successfully requested") |
|
|
|
node := sim.RandomUpNode() |
|
|
|
if live { |
|
|
|
item, ok := sim.NodeItem(node.ID, bucketKeyStore) |
|
|
|
//now upload the chunks to the selected random single node
|
|
|
|
if !ok { |
|
|
|
hashes, err := uploadFileToSingleNodeStore(node.ID(), chunkCount) |
|
|
|
return fmt.Errorf("No localstore") |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
conf.hashes = append(conf.hashes, hashes...) |
|
|
|
|
|
|
|
//finally map chunks to the closest addresses
|
|
|
|
|
|
|
|
log.Debug(fmt.Sprintf("Uploaded chunks for live syncing: %v", conf.hashes)) |
|
|
|
|
|
|
|
mapKeysToNodes(conf) |
|
|
|
|
|
|
|
log.Info(fmt.Sprintf("Uploaded %d chunks to random single node", chunkCount)) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
lstore := item.(*storage.LocalStore) |
|
|
|
|
|
|
|
hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
conf.hashes = append(conf.hashes, hashes...) |
|
|
|
|
|
|
|
mapKeysToNodes(conf) |
|
|
|
|
|
|
|
|
|
|
|
log.Info("Action terminated") |
|
|
|
if _, err := sim.WaitTillHealthy(ctx, 2); err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//check defines what will be checked during the test
|
|
|
|
|
|
|
|
check := func(ctx context.Context, id discover.NodeID) (bool, error) { |
|
|
|
|
|
|
|
select { |
|
|
|
|
|
|
|
case <-ctx.Done(): |
|
|
|
|
|
|
|
return false, ctx.Err() |
|
|
|
|
|
|
|
case e := <-disconnectC: |
|
|
|
|
|
|
|
log.Error(e.Error()) |
|
|
|
|
|
|
|
return false, fmt.Errorf("Disconnect event detected, network unhealthy") |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
log.Trace(fmt.Sprintf("Checking node: %s", id)) |
|
|
|
|
|
|
|
//select the local store for the given node
|
|
|
|
var gDir string |
|
|
|
//if there are more than one chunk, test only succeeds if all expected chunks are found
|
|
|
|
var globalStore *mockdb.GlobalStore |
|
|
|
allSuccess := true |
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
gDir, globalStore, err = createGlobalStore() |
|
|
|
//all the chunk indexes which are supposed to be found for this node
|
|
|
|
|
|
|
|
localChunks := conf.idToChunksMap[id] |
|
|
|
|
|
|
|
//for each expected chunk, check if it is in the local store
|
|
|
|
|
|
|
|
for _, ch := range localChunks { |
|
|
|
|
|
|
|
//get the real chunk by the index in the index array
|
|
|
|
|
|
|
|
chunk := conf.hashes[ch] |
|
|
|
|
|
|
|
log.Trace(fmt.Sprintf("node has chunk: %s:", chunk)) |
|
|
|
|
|
|
|
//check if the expected chunk is indeed in the localstore
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
if globalStore == nil { |
|
|
|
|
|
|
|
return false, fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
//use the globalStore if the mockStore should be used; in that case,
|
|
|
|
|
|
|
|
//the complete localStore stack is bypassed for getting the chunk
|
|
|
|
|
|
|
|
_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
//use the actual localstore
|
|
|
|
|
|
|
|
lstore := stores[id] |
|
|
|
|
|
|
|
_, err = lstore.Get(context.TODO(), chunk) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id)) |
|
|
|
return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil") |
|
|
|
allSuccess = false |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id)) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
defer os.RemoveAll(gDir) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// File retrieval check is repeated until all uploaded files are retrieved from all nodes
|
|
|
|
return allSuccess, nil |
|
|
|
// or until the timeout is reached.
|
|
|
|
} |
|
|
|
allSuccess := false |
|
|
|
|
|
|
|
for !allSuccess { |
|
|
|
//for each tick, run the checks on all nodes
|
|
|
|
for _, id := range nodeIDs { |
|
|
|
timingTicker := time.NewTicker(time.Second * 1) |
|
|
|
//for each expected chunk, check if it is in the local store
|
|
|
|
defer timingTicker.Stop() |
|
|
|
localChunks := conf.idToChunksMap[id] |
|
|
|
go func() { |
|
|
|
localSuccess := true |
|
|
|
for range timingTicker.C { |
|
|
|
for _, ch := range localChunks { |
|
|
|
for i := 0; i < len(ids); i++ { |
|
|
|
//get the real chunk by the index in the index array
|
|
|
|
log.Trace(fmt.Sprintf("triggering step %d, id %s", i, ids[i])) |
|
|
|
chunk := conf.hashes[ch] |
|
|
|
trigger <- ids[i] |
|
|
|
log.Trace(fmt.Sprintf("node has chunk: %s:", chunk)) |
|
|
|
|
|
|
|
//check if the expected chunk is indeed in the localstore
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
if *useMockStore { |
|
|
|
|
|
|
|
//use the globalStore if the mockStore should be used; in that case,
|
|
|
|
|
|
|
|
//the complete localStore stack is bypassed for getting the chunk
|
|
|
|
|
|
|
|
_, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
//use the actual localstore
|
|
|
|
|
|
|
|
item, ok := sim.NodeItem(id, bucketKeyStore) |
|
|
|
|
|
|
|
if !ok { |
|
|
|
|
|
|
|
return fmt.Errorf("Error accessing localstore") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
lstore := item.(*storage.LocalStore) |
|
|
|
|
|
|
|
_, err = lstore.Get(ctx, chunk) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id)) |
|
|
|
|
|
|
|
localSuccess = false |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
allSuccess = localSuccess |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}() |
|
|
|
if !allSuccess { |
|
|
|
|
|
|
|
return fmt.Errorf("Not all chunks succeeded!") |
|
|
|
log.Info("Starting simulation run...") |
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
timeout := MaxTimeout * time.Second |
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout) |
|
|
|
|
|
|
|
defer cancel() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//run the simulation
|
|
|
|
|
|
|
|
result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ |
|
|
|
|
|
|
|
Action: action, |
|
|
|
|
|
|
|
Trigger: trigger, |
|
|
|
|
|
|
|
Expect: &simulations.Expectation{ |
|
|
|
|
|
|
|
Nodes: ids, |
|
|
|
|
|
|
|
Check: check, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
if result.Error != nil { |
|
|
|
if result.Error != nil { |
|
|
|
return result.Error |
|
|
|
return result.Error |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
log.Info("Simulation terminated") |
|
|
|
log.Info("Simulation terminated") |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
@ -489,20 +451,9 @@ func runSyncTest(chunkCount int, nodeCount int, live bool, history bool) error { |
|
|
|
//issues `RequestSubscriptionMsg` to peers, based on po, by iterating over
|
|
|
|
//issues `RequestSubscriptionMsg` to peers, based on po, by iterating over
|
|
|
|
//the kademlia's `EachBin` function.
|
|
|
|
//the kademlia's `EachBin` function.
|
|
|
|
//returns the number of subscriptions requested
|
|
|
|
//returns the number of subscriptions requested
|
|
|
|
func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) { |
|
|
|
func startSyncing(r *Registry, conf *synctestConfig) (int, error) { |
|
|
|
var err error |
|
|
|
var err error |
|
|
|
|
|
|
|
|
|
|
|
if log.Lvl(*loglevel) == log.LvlDebug { |
|
|
|
|
|
|
|
//PeerPot for this node
|
|
|
|
|
|
|
|
addr := common.Bytes2Hex(r.addr.OAddr) |
|
|
|
|
|
|
|
pp := ppmap[addr] |
|
|
|
|
|
|
|
//call Healthy RPC
|
|
|
|
|
|
|
|
h := r.delivery.overlay.Healthy(pp) |
|
|
|
|
|
|
|
//print info
|
|
|
|
|
|
|
|
log.Debug(r.delivery.overlay.String()) |
|
|
|
|
|
|
|
log.Debug(fmt.Sprintf("IS HEALTHY: %t", h.GotNN && h.KnowNN && h.Full)) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
kad, ok := r.delivery.overlay.(*network.Kademlia) |
|
|
|
kad, ok := r.delivery.overlay.(*network.Kademlia) |
|
|
|
if !ok { |
|
|
|
if !ok { |
|
|
|
return 0, fmt.Errorf("Not a Kademlia!") |
|
|
|
return 0, fmt.Errorf("Not a Kademlia!") |
|
|
@ -512,14 +463,10 @@ func (r *TestRegistry) StartSyncing(ctx context.Context) (int, error) { |
|
|
|
//iterate over each bin and solicit needed subscription to bins
|
|
|
|
//iterate over each bin and solicit needed subscription to bins
|
|
|
|
kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool { |
|
|
|
kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool { |
|
|
|
//identify begin and start index of the bin(s) we want to subscribe to
|
|
|
|
//identify begin and start index of the bin(s) we want to subscribe to
|
|
|
|
log.Debug(fmt.Sprintf("Requesting subscription by: registry %s from peer %s for bin: %d", r.addr.ID(), conf.addrToIdMap[string(conn.Address())], po)) |
|
|
|
histRange := &Range{} |
|
|
|
var histRange *Range |
|
|
|
|
|
|
|
if history { |
|
|
|
|
|
|
|
histRange = &Range{} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
subCnt++ |
|
|
|
subCnt++ |
|
|
|
err = r.RequestSubscription(conf.addrToIdMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), live), histRange, Top) |
|
|
|
err = r.RequestSubscription(conf.addrToIDMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), true), histRange, Top) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err)) |
|
|
|
log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err)) |
|
|
|
return false |
|
|
|
return false |
|
|
@ -552,7 +499,7 @@ func mapKeysToNodes(conf *synctestConfig) { |
|
|
|
return false |
|
|
|
return false |
|
|
|
} |
|
|
|
} |
|
|
|
if pl == 256 || pl == po { |
|
|
|
if pl == 256 || pl == po { |
|
|
|
log.Trace(fmt.Sprintf("appending %s", conf.addrToIdMap[string(a)])) |
|
|
|
log.Trace(fmt.Sprintf("appending %s", conf.addrToIDMap[string(a)])) |
|
|
|
nns = append(nns, indexmap[string(a)]) |
|
|
|
nns = append(nns, indexmap[string(a)]) |
|
|
|
nodemap[string(a)] = append(nodemap[string(a)], i) |
|
|
|
nodemap[string(a)] = append(nodemap[string(a)], i) |
|
|
|
} |
|
|
|
} |
|
|
@ -567,26 +514,24 @@ func mapKeysToNodes(conf *synctestConfig) { |
|
|
|
} |
|
|
|
} |
|
|
|
for addr, chunks := range nodemap { |
|
|
|
for addr, chunks := range nodemap { |
|
|
|
//this selects which chunks are expected to be found with the given node
|
|
|
|
//this selects which chunks are expected to be found with the given node
|
|
|
|
conf.idToChunksMap[conf.addrToIdMap[addr]] = chunks |
|
|
|
conf.idToChunksMap[conf.addrToIDMap[addr]] = chunks |
|
|
|
} |
|
|
|
} |
|
|
|
log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap)) |
|
|
|
log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap)) |
|
|
|
conf.chunksToNodesMap = kmap |
|
|
|
conf.chunksToNodesMap = kmap |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//upload a file(chunks) to a single local node store
|
|
|
|
//upload a file(chunks) to a single local node store
|
|
|
|
func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int) ([]storage.Address, error) { |
|
|
|
func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int, lstore *storage.LocalStore) ([]storage.Address, error) { |
|
|
|
log.Debug(fmt.Sprintf("Uploading to node id: %s", id)) |
|
|
|
log.Debug(fmt.Sprintf("Uploading to node id: %s", id)) |
|
|
|
lstore := stores[id] |
|
|
|
|
|
|
|
size := chunkSize |
|
|
|
|
|
|
|
fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams()) |
|
|
|
fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams()) |
|
|
|
|
|
|
|
size := chunkSize |
|
|
|
var rootAddrs []storage.Address |
|
|
|
var rootAddrs []storage.Address |
|
|
|
for i := 0; i < chunkCount; i++ { |
|
|
|
for i := 0; i < chunkCount; i++ { |
|
|
|
ctx := context.TODO() |
|
|
|
rk, wait, err := fileStore.Store(context.TODO(), io.LimitReader(crand.Reader, int64(size)), int64(size), false) |
|
|
|
rk, wait, err := fileStore.Store(ctx, io.LimitReader(crand.Reader, int64(size)), int64(size), false) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
err = wait(ctx) |
|
|
|
err = wait(context.TODO()) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
@ -595,129 +540,3 @@ func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int) ([]storage. |
|
|
|
|
|
|
|
|
|
|
|
return rootAddrs, nil |
|
|
|
return rootAddrs, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//initialize a network from a snapshot
|
|
|
|
|
|
|
|
func initNetWithSnapshot(nodeCount int) (*simulations.Network, error) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var a adapters.NodeAdapter |
|
|
|
|
|
|
|
//add the streamer service to the node adapter
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if *adapter == "exec" { |
|
|
|
|
|
|
|
dirname, err := ioutil.TempDir(".", "") |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
a = adapters.NewExecAdapter(dirname) |
|
|
|
|
|
|
|
} else if *adapter == "tcp" { |
|
|
|
|
|
|
|
a = adapters.NewTCPAdapter(services) |
|
|
|
|
|
|
|
} else if *adapter == "sim" { |
|
|
|
|
|
|
|
a = adapters.NewSimAdapter(services) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.Info("Setting up Snapshot network") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
net := simulations.NewNetwork(a, &simulations.NetworkConfig{ |
|
|
|
|
|
|
|
ID: "0", |
|
|
|
|
|
|
|
DefaultService: "streamer", |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
f, err := os.Open(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
defer f.Close() |
|
|
|
|
|
|
|
jsonbyte, err := ioutil.ReadAll(f) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var snap simulations.Snapshot |
|
|
|
|
|
|
|
err = json.Unmarshal(jsonbyte, &snap) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//the snapshot probably has the property EnableMsgEvents not set
|
|
|
|
|
|
|
|
//just in case, set it to true!
|
|
|
|
|
|
|
|
//(we need this to wait for messages before uploading)
|
|
|
|
|
|
|
|
for _, n := range snap.Nodes { |
|
|
|
|
|
|
|
n.Node.Config.EnableMsgEvents = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
log.Info("Waiting for p2p connections to be established...") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//now we can load the snapshot
|
|
|
|
|
|
|
|
err = net.Load(&snap) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
log.Info("Snapshot loaded") |
|
|
|
|
|
|
|
return net, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//we want to wait for subscriptions to be established before uploading to test
|
|
|
|
|
|
|
|
//that live syncing is working correctly
|
|
|
|
|
|
|
|
func watchSubscriptionEvents(ctx context.Context, id discover.NodeID, client *rpc.Client, errc chan error, quitC chan struct{}) (doneC <-chan struct{}) { |
|
|
|
|
|
|
|
events := make(chan *p2p.PeerEvent) |
|
|
|
|
|
|
|
sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
log.Error(err.Error()) |
|
|
|
|
|
|
|
errc <- fmt.Errorf("error getting peer events for node %v: %s", id, err) |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
c := make(chan struct{}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
|
|
|
defer func() { |
|
|
|
|
|
|
|
log.Trace("watch subscription events: unsubscribe", "id", id) |
|
|
|
|
|
|
|
sub.Unsubscribe() |
|
|
|
|
|
|
|
close(c) |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for { |
|
|
|
|
|
|
|
select { |
|
|
|
|
|
|
|
case <-quitC: |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
case <-ctx.Done(): |
|
|
|
|
|
|
|
select { |
|
|
|
|
|
|
|
case errc <- ctx.Err(): |
|
|
|
|
|
|
|
case <-quitC: |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
case e := <-events: |
|
|
|
|
|
|
|
//just catch SubscribeMsg
|
|
|
|
|
|
|
|
if e.Type == p2p.PeerEventTypeMsgRecv && e.Protocol == "stream" && e.MsgCode != nil && *e.MsgCode == 4 { |
|
|
|
|
|
|
|
errc <- nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
case err := <-sub.Err(): |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
select { |
|
|
|
|
|
|
|
case errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err): |
|
|
|
|
|
|
|
case <-quitC: |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}() |
|
|
|
|
|
|
|
return c |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//create a local store for the given node
|
|
|
|
|
|
|
|
func createTestLocalStorageForId(id discover.NodeID, addr *network.BzzAddr) (storage.ChunkStore, error) { |
|
|
|
|
|
|
|
var datadir string |
|
|
|
|
|
|
|
var err error |
|
|
|
|
|
|
|
datadir, err = ioutil.TempDir("", fmt.Sprintf("syncer-test-%s", id.TerminalString())) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
datadirs[id] = datadir |
|
|
|
|
|
|
|
var store storage.ChunkStore |
|
|
|
|
|
|
|
params := storage.NewDefaultLocalStoreParams() |
|
|
|
|
|
|
|
params.ChunkDbPath = datadir |
|
|
|
|
|
|
|
params.BaseKey = addr.Over() |
|
|
|
|
|
|
|
store, err = storage.NewTestLocalStoreForAddr(params) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return store, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|