github.com/daragao/go-ethereum@v1.8.14-0.20180809141559-45eaef243198/swarm/network/stream/snapshot_sync_test.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 package stream 17 18 import ( 19 "context" 20 crand "crypto/rand" 21 "fmt" 22 "io" 23 "os" 24 "sync" 25 "testing" 26 "time" 27 28 "github.com/ethereum/go-ethereum/common" 29 "github.com/ethereum/go-ethereum/log" 30 "github.com/ethereum/go-ethereum/node" 31 "github.com/ethereum/go-ethereum/p2p" 32 "github.com/ethereum/go-ethereum/p2p/discover" 33 "github.com/ethereum/go-ethereum/p2p/simulations/adapters" 34 "github.com/ethereum/go-ethereum/swarm/network" 35 "github.com/ethereum/go-ethereum/swarm/network/simulation" 36 "github.com/ethereum/go-ethereum/swarm/pot" 37 "github.com/ethereum/go-ethereum/swarm/state" 38 "github.com/ethereum/go-ethereum/swarm/storage" 39 mockdb "github.com/ethereum/go-ethereum/swarm/storage/mock/db" 40 ) 41 42 const testMinProxBinSize = 2 43 const MaxTimeout = 600 44 45 type synctestConfig struct { 46 addrs [][]byte 47 hashes []storage.Address 48 idToChunksMap map[discover.NodeID][]int 49 chunksToNodesMap map[string][]int 50 addrToIDMap map[string]discover.NodeID 51 } 52 53 //This test is a syncing test for nodes. 54 //One node is randomly selected to be the pivot node. 55 //A configurable number of chunks and nodes can be 56 //provided to the test, the number of chunks is uploaded 57 //to the pivot node, and we check that nodes get the chunks 58 //they are expected to store based on the syncing protocol. 59 //Number of chunks and nodes can be provided via commandline too. 60 func TestSyncingViaGlobalSync(t *testing.T) { 61 //if nodes/chunks have been provided via commandline, 62 //run the tests with these values 63 if *nodes != 0 && *chunks != 0 { 64 log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) 65 testSyncingViaGlobalSync(t, *chunks, *nodes) 66 } else { 67 var nodeCnt []int 68 var chnkCnt []int 69 //if the `longrunning` flag has been provided 70 //run more test combinations 71 if *longrunning { 72 chnkCnt = []int{1, 8, 32, 256, 1024} 73 nodeCnt = []int{16, 32, 64, 128, 256} 74 } else { 75 //default test 76 chnkCnt = []int{4, 32} 77 nodeCnt = []int{32, 16} 78 } 79 for _, chnk := range chnkCnt { 80 for _, n := range nodeCnt { 81 log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) 82 testSyncingViaGlobalSync(t, chnk, n) 83 } 84 } 85 } 86 } 87 88 func TestSyncingViaDirectSubscribe(t *testing.T) { 89 //if nodes/chunks have been provided via commandline, 90 //run the tests with these values 91 if *nodes != 0 && *chunks != 0 { 92 log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) 93 err := testSyncingViaDirectSubscribe(*chunks, *nodes) 94 if err != nil { 95 t.Fatal(err) 96 } 97 } else { 98 var nodeCnt []int 99 var chnkCnt []int 100 //if the `longrunning` flag has been provided 101 //run more test combinations 102 if *longrunning { 103 chnkCnt = []int{1, 8, 32, 256, 1024} 104 nodeCnt = []int{32, 16} 105 } else { 106 //default test 107 chnkCnt = []int{4, 32} 108 nodeCnt = []int{32, 16} 109 } 110 for _, chnk := range chnkCnt { 111 for _, n := range nodeCnt { 112 log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) 113 err := testSyncingViaDirectSubscribe(chnk, n) 114 if err != nil { 115 t.Fatal(err) 116 } 117 } 118 } 119 } 120 } 121 122 func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) { 123 sim := simulation.New(map[string]simulation.ServiceFunc{ 124 "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 125 126 id := ctx.Config.ID 127 addr := network.NewAddrFromNodeID(id) 128 store, datadir, err := createTestLocalStorageForID(id, addr) 129 if err != nil { 130 return nil, nil, err 131 } 132 bucket.Store(bucketKeyStore, store) 133 cleanup = func() { 134 os.RemoveAll(datadir) 135 store.Close() 136 } 137 localStore := store.(*storage.LocalStore) 138 db := storage.NewDBAPI(localStore) 139 kad := network.NewKademlia(addr.Over(), network.NewKadParams()) 140 delivery := NewDelivery(kad, db) 141 142 r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), &RegistryOptions{ 143 DoSync: true, 144 SyncUpdateDelay: 3 * time.Second, 145 }) 146 bucket.Store(bucketKeyRegistry, r) 147 148 return r, cleanup, nil 149 150 }, 151 }) 152 defer sim.Close() 153 154 log.Info("Initializing test config") 155 156 conf := &synctestConfig{} 157 //map of discover ID to indexes of chunks expected at that ID 158 conf.idToChunksMap = make(map[discover.NodeID][]int) 159 //map of overlay address to discover ID 160 conf.addrToIDMap = make(map[string]discover.NodeID) 161 //array where the generated chunk hashes will be stored 162 conf.hashes = make([]storage.Address, 0) 163 164 err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute) 170 defer cancelSimRun() 171 172 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { 173 nodeIDs := sim.UpNodeIDs() 174 for _, n := range nodeIDs { 175 //get the kademlia overlay address from this ID 176 a := network.ToOverlayAddr(n.Bytes()) 177 //append it to the array of all overlay addresses 178 conf.addrs = append(conf.addrs, a) 179 //the proximity calculation is on overlay addr, 180 //the p2p/simulations check func triggers on discover.NodeID, 181 //so we need to know which overlay addr maps to which nodeID 182 conf.addrToIDMap[string(a)] = n 183 } 184 185 //get the the node at that index 186 //this is the node selected for upload 187 node := sim.RandomUpNode() 188 item, ok := sim.NodeItem(node.ID, bucketKeyStore) 189 if !ok { 190 return fmt.Errorf("No localstore") 191 } 192 lstore := item.(*storage.LocalStore) 193 hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore) 194 if err != nil { 195 return err 196 } 197 conf.hashes = append(conf.hashes, hashes...) 198 mapKeysToNodes(conf) 199 200 if _, err := sim.WaitTillHealthy(ctx, 2); err != nil { 201 return err 202 } 203 204 // File retrieval check is repeated until all uploaded files are retrieved from all nodes 205 // or until the timeout is reached. 206 allSuccess := false 207 var gDir string 208 var globalStore *mockdb.GlobalStore 209 if *useMockStore { 210 gDir, globalStore, err = createGlobalStore() 211 if err != nil { 212 return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil") 213 } 214 defer func() { 215 os.RemoveAll(gDir) 216 err := globalStore.Close() 217 if err != nil { 218 log.Error("Error closing global store! %v", "err", err) 219 } 220 }() 221 } 222 for !allSuccess { 223 for _, id := range nodeIDs { 224 //for each expected chunk, check if it is in the local store 225 localChunks := conf.idToChunksMap[id] 226 localSuccess := true 227 for _, ch := range localChunks { 228 //get the real chunk by the index in the index array 229 chunk := conf.hashes[ch] 230 log.Trace(fmt.Sprintf("node has chunk: %s:", chunk)) 231 //check if the expected chunk is indeed in the localstore 232 var err error 233 if *useMockStore { 234 //use the globalStore if the mockStore should be used; in that case, 235 //the complete localStore stack is bypassed for getting the chunk 236 _, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk) 237 } else { 238 //use the actual localstore 239 item, ok := sim.NodeItem(id, bucketKeyStore) 240 if !ok { 241 return fmt.Errorf("Error accessing localstore") 242 } 243 lstore := item.(*storage.LocalStore) 244 _, err = lstore.Get(ctx, chunk) 245 } 246 if err != nil { 247 log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id)) 248 localSuccess = false 249 // Do not get crazy with logging the warn message 250 time.Sleep(500 * time.Millisecond) 251 } else { 252 log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id)) 253 } 254 } 255 allSuccess = localSuccess 256 } 257 } 258 if !allSuccess { 259 return fmt.Errorf("Not all chunks succeeded!") 260 } 261 return nil 262 }) 263 264 if result.Error != nil { 265 t.Fatal(result.Error) 266 } 267 } 268 269 /* 270 The test generates the given number of chunks 271 272 For every chunk generated, the nearest node addresses 273 are identified, we verify that the nodes closer to the 274 chunk addresses actually do have the chunks in their local stores. 275 276 The test loads a snapshot file to construct the swarm network, 277 assuming that the snapshot file identifies a healthy 278 kademlia network. The snapshot should have 'streamer' in its service list. 279 */ 280 func testSyncingViaDirectSubscribe(chunkCount int, nodeCount int) error { 281 sim := simulation.New(map[string]simulation.ServiceFunc{ 282 "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 283 284 id := ctx.Config.ID 285 addr := network.NewAddrFromNodeID(id) 286 store, datadir, err := createTestLocalStorageForID(id, addr) 287 if err != nil { 288 return nil, nil, err 289 } 290 bucket.Store(bucketKeyStore, store) 291 cleanup = func() { 292 os.RemoveAll(datadir) 293 store.Close() 294 } 295 localStore := store.(*storage.LocalStore) 296 db := storage.NewDBAPI(localStore) 297 kad := network.NewKademlia(addr.Over(), network.NewKadParams()) 298 delivery := NewDelivery(kad, db) 299 300 r := NewRegistry(addr, delivery, db, state.NewInmemoryStore(), nil) 301 bucket.Store(bucketKeyRegistry, r) 302 303 fileStore := storage.NewFileStore(storage.NewNetStore(localStore, nil), storage.NewFileStoreParams()) 304 bucket.Store(bucketKeyFileStore, fileStore) 305 306 return r, cleanup, nil 307 308 }, 309 }) 310 defer sim.Close() 311 312 ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute) 313 defer cancelSimRun() 314 315 conf := &synctestConfig{} 316 //map of discover ID to indexes of chunks expected at that ID 317 conf.idToChunksMap = make(map[discover.NodeID][]int) 318 //map of overlay address to discover ID 319 conf.addrToIDMap = make(map[string]discover.NodeID) 320 //array where the generated chunk hashes will be stored 321 conf.hashes = make([]storage.Address, 0) 322 323 err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) 324 if err != nil { 325 return err 326 } 327 328 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { 329 nodeIDs := sim.UpNodeIDs() 330 for _, n := range nodeIDs { 331 //get the kademlia overlay address from this ID 332 a := network.ToOverlayAddr(n.Bytes()) 333 //append it to the array of all overlay addresses 334 conf.addrs = append(conf.addrs, a) 335 //the proximity calculation is on overlay addr, 336 //the p2p/simulations check func triggers on discover.NodeID, 337 //so we need to know which overlay addr maps to which nodeID 338 conf.addrToIDMap[string(a)] = n 339 } 340 341 var subscriptionCount int 342 343 filter := simulation.NewPeerEventsFilter().Type(p2p.PeerEventTypeMsgRecv).Protocol("stream").MsgCode(4) 344 eventC := sim.PeerEvents(ctx, nodeIDs, filter) 345 346 for j, node := range nodeIDs { 347 log.Trace(fmt.Sprintf("Start syncing subscriptions: %d", j)) 348 //start syncing! 349 item, ok := sim.NodeItem(node, bucketKeyRegistry) 350 if !ok { 351 return fmt.Errorf("No registry") 352 } 353 registry := item.(*Registry) 354 355 var cnt int 356 cnt, err = startSyncing(registry, conf) 357 if err != nil { 358 return err 359 } 360 //increment the number of subscriptions we need to wait for 361 //by the count returned from startSyncing (SYNC subscriptions) 362 subscriptionCount += cnt 363 } 364 365 for e := range eventC { 366 if e.Error != nil { 367 return e.Error 368 } 369 subscriptionCount-- 370 if subscriptionCount == 0 { 371 break 372 } 373 } 374 //select a random node for upload 375 node := sim.RandomUpNode() 376 item, ok := sim.NodeItem(node.ID, bucketKeyStore) 377 if !ok { 378 return fmt.Errorf("No localstore") 379 } 380 lstore := item.(*storage.LocalStore) 381 hashes, err := uploadFileToSingleNodeStore(node.ID, chunkCount, lstore) 382 if err != nil { 383 return err 384 } 385 conf.hashes = append(conf.hashes, hashes...) 386 mapKeysToNodes(conf) 387 388 if _, err := sim.WaitTillHealthy(ctx, 2); err != nil { 389 return err 390 } 391 392 var gDir string 393 var globalStore *mockdb.GlobalStore 394 if *useMockStore { 395 gDir, globalStore, err = createGlobalStore() 396 if err != nil { 397 return fmt.Errorf("Something went wrong; using mockStore enabled but globalStore is nil") 398 } 399 defer os.RemoveAll(gDir) 400 } 401 // File retrieval check is repeated until all uploaded files are retrieved from all nodes 402 // or until the timeout is reached. 403 allSuccess := false 404 for !allSuccess { 405 for _, id := range nodeIDs { 406 //for each expected chunk, check if it is in the local store 407 localChunks := conf.idToChunksMap[id] 408 localSuccess := true 409 for _, ch := range localChunks { 410 //get the real chunk by the index in the index array 411 chunk := conf.hashes[ch] 412 log.Trace(fmt.Sprintf("node has chunk: %s:", chunk)) 413 //check if the expected chunk is indeed in the localstore 414 var err error 415 if *useMockStore { 416 //use the globalStore if the mockStore should be used; in that case, 417 //the complete localStore stack is bypassed for getting the chunk 418 _, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk) 419 } else { 420 //use the actual localstore 421 item, ok := sim.NodeItem(id, bucketKeyStore) 422 if !ok { 423 return fmt.Errorf("Error accessing localstore") 424 } 425 lstore := item.(*storage.LocalStore) 426 _, err = lstore.Get(ctx, chunk) 427 } 428 if err != nil { 429 log.Warn(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id)) 430 localSuccess = false 431 // Do not get crazy with logging the warn message 432 time.Sleep(500 * time.Millisecond) 433 } else { 434 log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id)) 435 } 436 } 437 allSuccess = localSuccess 438 } 439 } 440 if !allSuccess { 441 return fmt.Errorf("Not all chunks succeeded!") 442 } 443 return nil 444 }) 445 446 if result.Error != nil { 447 return result.Error 448 } 449 450 log.Info("Simulation terminated") 451 return nil 452 } 453 454 //the server func to start syncing 455 //issues `RequestSubscriptionMsg` to peers, based on po, by iterating over 456 //the kademlia's `EachBin` function. 457 //returns the number of subscriptions requested 458 func startSyncing(r *Registry, conf *synctestConfig) (int, error) { 459 var err error 460 461 kad, ok := r.delivery.overlay.(*network.Kademlia) 462 if !ok { 463 return 0, fmt.Errorf("Not a Kademlia!") 464 } 465 466 subCnt := 0 467 //iterate over each bin and solicit needed subscription to bins 468 kad.EachBin(r.addr.Over(), pof, 0, func(conn network.OverlayConn, po int) bool { 469 //identify begin and start index of the bin(s) we want to subscribe to 470 histRange := &Range{} 471 472 subCnt++ 473 err = r.RequestSubscription(conf.addrToIDMap[string(conn.Address())], NewStream("SYNC", FormatSyncBinKey(uint8(po)), true), histRange, Top) 474 if err != nil { 475 log.Error(fmt.Sprintf("Error in RequestSubsciption! %v", err)) 476 return false 477 } 478 return true 479 480 }) 481 return subCnt, nil 482 } 483 484 //map chunk keys to addresses which are responsible 485 func mapKeysToNodes(conf *synctestConfig) { 486 kmap := make(map[string][]int) 487 nodemap := make(map[string][]int) 488 //build a pot for chunk hashes 489 np := pot.NewPot(nil, 0) 490 indexmap := make(map[string]int) 491 for i, a := range conf.addrs { 492 indexmap[string(a)] = i 493 np, _, _ = pot.Add(np, a, pof) 494 } 495 //for each address, run EachNeighbour on the chunk hashes pot to identify closest nodes 496 log.Trace(fmt.Sprintf("Generated hash chunk(s): %v", conf.hashes)) 497 for i := 0; i < len(conf.hashes); i++ { 498 pl := 256 //highest possible proximity 499 var nns []int 500 np.EachNeighbour([]byte(conf.hashes[i]), pof, func(val pot.Val, po int) bool { 501 a := val.([]byte) 502 if pl < 256 && pl != po { 503 return false 504 } 505 if pl == 256 || pl == po { 506 log.Trace(fmt.Sprintf("appending %s", conf.addrToIDMap[string(a)])) 507 nns = append(nns, indexmap[string(a)]) 508 nodemap[string(a)] = append(nodemap[string(a)], i) 509 } 510 if pl == 256 && len(nns) >= testMinProxBinSize { 511 //maxProxBinSize has been reached at this po, so save it 512 //we will add all other nodes at the same po 513 pl = po 514 } 515 return true 516 }) 517 kmap[string(conf.hashes[i])] = nns 518 } 519 for addr, chunks := range nodemap { 520 //this selects which chunks are expected to be found with the given node 521 conf.idToChunksMap[conf.addrToIDMap[addr]] = chunks 522 } 523 log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap)) 524 conf.chunksToNodesMap = kmap 525 } 526 527 //upload a file(chunks) to a single local node store 528 func uploadFileToSingleNodeStore(id discover.NodeID, chunkCount int, lstore *storage.LocalStore) ([]storage.Address, error) { 529 log.Debug(fmt.Sprintf("Uploading to node id: %s", id)) 530 fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams()) 531 size := chunkSize 532 var rootAddrs []storage.Address 533 for i := 0; i < chunkCount; i++ { 534 rk, wait, err := fileStore.Store(context.TODO(), io.LimitReader(crand.Reader, int64(size)), int64(size), false) 535 if err != nil { 536 return nil, err 537 } 538 err = wait(context.TODO()) 539 if err != nil { 540 return nil, err 541 } 542 rootAddrs = append(rootAddrs, (rk)) 543 } 544 545 return rootAddrs, nil 546 }