github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/swarm/network/stream/visualized_snapshot_sync_sim_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 17 // +build withserver 18 19 package stream 20 21 import ( 22 "bytes" 23 "context" 24 "errors" 25 "fmt" 26 "io" 27 "os" 28 "sync" 29 "testing" 30 "time" 31 32 "github.com/ShyftNetwork/go-empyrean/node" 33 "github.com/ShyftNetwork/go-empyrean/p2p" 34 "github.com/ShyftNetwork/go-empyrean/p2p/enode" 35 "github.com/ShyftNetwork/go-empyrean/p2p/protocols" 36 "github.com/ShyftNetwork/go-empyrean/p2p/simulations" 37 "github.com/ShyftNetwork/go-empyrean/p2p/simulations/adapters" 38 "github.com/ShyftNetwork/go-empyrean/rlp" 39 "github.com/ShyftNetwork/go-empyrean/swarm/log" 40 "github.com/ShyftNetwork/go-empyrean/swarm/network" 41 "github.com/ShyftNetwork/go-empyrean/swarm/network/simulation" 42 "github.com/ShyftNetwork/go-empyrean/swarm/state" 43 "github.com/ShyftNetwork/go-empyrean/swarm/storage" 44 ) 45 46 /* 47 The tests in this file need to be executed with 48 49 -tags=withserver 50 51 Also, they will stall if executed stand-alone, because they wait 52 for the visualization frontend to send a POST /runsim message. 53 */ 54 55 //setup the sim, evaluate nodeCount and chunkCount and create the sim 56 func setupSim(serviceMap map[string]simulation.ServiceFunc) (int, int, *simulation.Simulation) { 57 nodeCount := *nodes 58 chunkCount := *chunks 59 60 if nodeCount == 0 || chunkCount == 0 { 61 nodeCount = 32 62 chunkCount = 1 63 } 64 65 //setup the simulation with server, which means the sim won't run 66 //until it receives a POST /runsim from the frontend 67 sim := simulation.New(serviceMap).WithServer(":8888") 68 return nodeCount, chunkCount, sim 69 } 70 71 //watch for disconnections and wait for healthy 72 func watchSim(sim *simulation.Simulation) (context.Context, context.CancelFunc) { 73 ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute) 74 75 if _, err := sim.WaitTillHealthy(ctx); err != nil { 76 panic(err) 77 } 78 79 disconnections := sim.PeerEvents( 80 context.Background(), 81 sim.NodeIDs(), 82 simulation.NewPeerEventsFilter().Drop(), 83 ) 84 85 go func() { 86 for d := range disconnections { 87 log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID) 88 panic("unexpected disconnect") 89 cancelSimRun() 90 } 91 }() 92 93 return ctx, cancelSimRun 94 } 95 96 //This test requests bogus hashes into the network 97 func TestNonExistingHashesWithServer(t *testing.T) { 98 99 nodeCount, _, sim := setupSim(retrievalSimServiceMap) 100 defer sim.Close() 101 102 err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) 103 if err != nil { 104 panic(err) 105 } 106 107 ctx, cancelSimRun := watchSim(sim) 108 defer cancelSimRun() 109 110 //in order to get some meaningful visualization, it is beneficial 111 //to define a minimum duration of this test 112 testDuration := 20 * time.Second 113 114 result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { 115 //check on the node's FileStore (netstore) 116 id := sim.Net.GetRandomUpNode().ID() 117 item, ok := sim.NodeItem(id, bucketKeyFileStore) 118 if !ok { 119 t.Fatalf("No filestore") 120 } 121 fileStore := item.(*storage.FileStore) 122 //create a bogus hash 123 fakeHash := storage.GenerateRandomChunk(1000).Address() 124 //try to retrieve it - will propagate RetrieveRequestMsg into the network 125 reader, _ := fileStore.Retrieve(context.TODO(), fakeHash) 126 if _, err := reader.Size(ctx, nil); err != nil { 127 log.Debug("expected error for non-existing chunk") 128 } 129 //sleep so that the frontend can have something to display 130 time.Sleep(testDuration) 131 132 return nil 133 }) 134 if result.Error != nil { 135 sendSimTerminatedEvent(sim) 136 t.Fatal(result.Error) 137 } 138 139 sendSimTerminatedEvent(sim) 140 141 } 142 143 //send a termination event to the frontend 144 func sendSimTerminatedEvent(sim *simulation.Simulation) { 145 evt := &simulations.Event{ 146 Type: EventTypeSimTerminated, 147 Control: false, 148 } 149 sim.Net.Events().Send(evt) 150 } 151 152 //This test is the same as the snapshot sync test, 153 //but with a HTTP server 154 //It also sends some custom events so that the frontend 155 //can visualize messages like SendOfferedMsg, WantedHashesMsg, DeliveryMsg 156 func TestSnapshotSyncWithServer(t *testing.T) { 157 //t.Skip("temporarily disabled as simulations.WaitTillHealthy cannot be trusted") 158 159 //define a wrapper object to be able to pass around data 160 wrapper := &netWrapper{} 161 162 nodeCount := *nodes 163 chunkCount := *chunks 164 165 if nodeCount == 0 || chunkCount == 0 { 166 nodeCount = 32 167 chunkCount = 1 168 } 169 170 log.Info(fmt.Sprintf("Running the simulation with %d nodes and %d chunks", nodeCount, chunkCount)) 171 172 sim := simulation.New(map[string]simulation.ServiceFunc{ 173 "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { 174 n := ctx.Config.Node() 175 addr := network.NewAddr(n) 176 store, datadir, err := createTestLocalStorageForID(n.ID(), addr) 177 if err != nil { 178 return nil, nil, err 179 } 180 bucket.Store(bucketKeyStore, store) 181 localStore := store.(*storage.LocalStore) 182 netStore, err := storage.NewNetStore(localStore, nil) 183 if err != nil { 184 return nil, nil, err 185 } 186 kad := network.NewKademlia(addr.Over(), network.NewKadParams()) 187 delivery := NewDelivery(kad, netStore) 188 netStore.NewNetFetcherFunc = network.NewFetcherFactory(dummyRequestFromPeers, true).New 189 190 r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ 191 Retrieval: RetrievalDisabled, 192 Syncing: SyncingAutoSubscribe, 193 SyncUpdateDelay: 3 * time.Second, 194 }, nil) 195 196 tr := &testRegistry{ 197 Registry: r, 198 w: wrapper, 199 } 200 201 bucket.Store(bucketKeyRegistry, tr) 202 203 cleanup = func() { 204 netStore.Close() 205 tr.Close() 206 os.RemoveAll(datadir) 207 } 208 209 return tr, cleanup, nil 210 }, 211 }).WithServer(":8888") //start with the HTTP server 212 213 nodeCount, chunkCount, sim := setupSim(simServiceMap) 214 defer sim.Close() 215 216 log.Info("Initializing test config") 217 218 conf := &synctestConfig{} 219 //map of discover ID to indexes of chunks expected at that ID 220 conf.idToChunksMap = make(map[enode.ID][]int) 221 //map of overlay address to discover ID 222 conf.addrToIDMap = make(map[string]enode.ID) 223 //array where the generated chunk hashes will be stored 224 conf.hashes = make([]storage.Address, 0) 225 //pass the network to the wrapper object 226 wrapper.setNetwork(sim.Net) 227 err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) 228 if err != nil { 229 panic(err) 230 } 231 232 ctx, cancelSimRun := watchSim(sim) 233 defer cancelSimRun() 234 235 //run the sim 236 result := runSim(conf, ctx, sim, chunkCount) 237 238 //send terminated event 239 evt := &simulations.Event{ 240 Type: EventTypeSimTerminated, 241 Control: false, 242 } 243 go sim.Net.Events().Send(evt) 244 245 if result.Error != nil { 246 panic(result.Error) 247 } 248 log.Info("Simulation ended") 249 } 250 251 //testRegistry embeds registry 252 //it allows to replace the protocol run function 253 type testRegistry struct { 254 *Registry 255 w *netWrapper 256 } 257 258 //Protocols replaces the protocol's run function 259 func (tr *testRegistry) Protocols() []p2p.Protocol { 260 regProto := tr.Registry.Protocols() 261 //set the `stream` protocol's run function with the testRegistry's one 262 regProto[0].Run = tr.runProto 263 return regProto 264 } 265 266 //runProto is the new overwritten protocol's run function for this test 267 func (tr *testRegistry) runProto(p *p2p.Peer, rw p2p.MsgReadWriter) error { 268 //create a custom rw message ReadWriter 269 testRw := &testMsgReadWriter{ 270 MsgReadWriter: rw, 271 Peer: p, 272 w: tr.w, 273 Registry: tr.Registry, 274 } 275 //now run the actual upper layer `Registry`'s protocol function 276 return tr.runProtocol(p, testRw) 277 } 278 279 //testMsgReadWriter is a custom rw 280 //it will allow us to re-use the message twice 281 type testMsgReadWriter struct { 282 *Registry 283 p2p.MsgReadWriter 284 *p2p.Peer 285 w *netWrapper 286 } 287 288 //netWrapper wrapper object so we can pass data around 289 type netWrapper struct { 290 net *simulations.Network 291 } 292 293 //set the network to the wrapper for later use (used inside the custom rw) 294 func (w *netWrapper) setNetwork(n *simulations.Network) { 295 w.net = n 296 } 297 298 //get he network from the wrapper (used inside the custom rw) 299 func (w *netWrapper) getNetwork() *simulations.Network { 300 return w.net 301 } 302 303 // ReadMsg reads a message from the underlying MsgReadWriter and emits a 304 // "message received" event 305 //we do this because we are interested in the Payload of the message for custom use 306 //in this test, but messages can only be consumed once (stream io.Reader) 307 func (ev *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { 308 //read the message from the underlying rw 309 msg, err := ev.MsgReadWriter.ReadMsg() 310 if err != nil { 311 return msg, err 312 } 313 314 //don't do anything with message codes we actually are not needing/reading 315 subCodes := []uint64{1, 2, 10} 316 found := false 317 for _, c := range subCodes { 318 if c == msg.Code { 319 found = true 320 } 321 } 322 //just return if not a msg code we are interested in 323 if !found { 324 return msg, nil 325 } 326 327 //we use a io.TeeReader so that we can read the message twice 328 //the Payload is a io.Reader, so if we read from it, the actual protocol handler 329 //cannot access it anymore. 330 //But we need that handler to be able to consume the message as normal, 331 //as if we would not do anything here with that message 332 var buf bytes.Buffer 333 tee := io.TeeReader(msg.Payload, &buf) 334 335 mcp := &p2p.Msg{ 336 Code: msg.Code, 337 Size: msg.Size, 338 ReceivedAt: msg.ReceivedAt, 339 Payload: tee, 340 } 341 //assign the copy for later use 342 msg.Payload = &buf 343 344 //now let's look into the message 345 var wmsg protocols.WrappedMsg 346 err = mcp.Decode(&wmsg) 347 if err != nil { 348 log.Error(err.Error()) 349 return msg, err 350 } 351 //create a new message from the code 352 val, ok := ev.Registry.GetSpec().NewMsg(mcp.Code) 353 if !ok { 354 return msg, errors.New(fmt.Sprintf("Invalid message code: %v", msg.Code)) 355 } 356 //decode it 357 if err := rlp.DecodeBytes(wmsg.Payload, val); err != nil { 358 return msg, errors.New(fmt.Sprintf("Decoding error <= %v: %v", msg, err)) 359 } 360 //now for every message type we are interested in, create a custom event and send it 361 var evt *simulations.Event 362 switch val := val.(type) { 363 case *OfferedHashesMsg: 364 evt = &simulations.Event{ 365 Type: EventTypeChunkOffered, 366 Node: ev.w.getNetwork().GetNode(ev.ID()), 367 Control: false, 368 Data: val.Hashes, 369 } 370 case *WantedHashesMsg: 371 evt = &simulations.Event{ 372 Type: EventTypeChunkWanted, 373 Node: ev.w.getNetwork().GetNode(ev.ID()), 374 Control: false, 375 } 376 case *ChunkDeliveryMsgSyncing: 377 evt = &simulations.Event{ 378 Type: EventTypeChunkDelivered, 379 Node: ev.w.getNetwork().GetNode(ev.ID()), 380 Control: false, 381 Data: val.Addr.String(), 382 } 383 } 384 if evt != nil { 385 //send custom event to feed; frontend will listen to it and display 386 ev.w.getNetwork().Events().Send(evt) 387 } 388 return msg, nil 389 }