github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/gossip_test.go (about) 1 package holochain 2 3 import ( 4 "fmt" 5 . "github.com/holochain/holochain-proto/hash" 6 peer "github.com/libp2p/go-libp2p-peer" 7 ma "github.com/multiformats/go-multiaddr" 8 . "github.com/smartystreets/goconvey/convey" 9 "testing" 10 "time" 11 ) 12 13 /* 14 @TODO add setup for gossip that adds entry and meta entry so we have something 15 to gossip about. Currently test is ActionReceiver test 16 17 func TestGossipReceiver(t *testing.T) { 18 d, _, h := PrepareTestChain("test") 19 defer CleanupTestChain(h,d) 20 h.dht.SetupDHT() 21 22 }*/ 23 24 func TestGetGossipers(t *testing.T) { 25 nodesCount := 20 26 mt := setupMultiNodeTesting(nodesCount) 27 defer mt.cleanupMultiNodeTesting() 28 nodes := mt.nodes 29 h := nodes[0] 30 dht := h.dht 31 Convey("should return an empty list if none availabled", t, func() { 32 glist, err := dht.getGossipers() 33 So(err, ShouldBeNil) 34 So(len(glist), ShouldEqual, 0) 35 }) 36 37 starConnect(t, mt.ctx, nodes, nodesCount) 38 39 var err error 40 var glist []peer.ID 41 Convey("should return all peers when redundancy factor is 0", t, func() { 42 So(h.nucleus.dna.DHTConfig.RedundancyFactor, ShouldEqual, 0) 43 glist, err = dht.getGossipers() 44 So(err, ShouldBeNil) 45 So(len(glist), ShouldEqual, nodesCount-1) 46 }) 47 48 Convey("should return neighborhood size peers when neighborhood size is not 0", t, func() { 49 h.nucleus.dna.DHTConfig.RedundancyFactor = 5 50 glist, err = dht.getGossipers() 51 So(err, ShouldBeNil) 52 So(len(glist), ShouldEqual, 5) 53 }) 54 55 Convey("should return list sorted by closeness to me", t, func() { 56 So(h.node.Distance(glist[0]).Cmp(h.node.Distance(glist[1])), ShouldBeLessThanOrEqualTo, 0) 57 So(h.node.Distance(glist[1]).Cmp(h.node.Distance(glist[2])), ShouldBeLessThanOrEqualTo, 0) 58 So(h.node.Distance(glist[2]).Cmp(h.node.Distance(glist[3])), ShouldBeLessThanOrEqualTo, 0) 59 So(h.node.Distance(glist[3]).Cmp(h.node.Distance(glist[4])), ShouldBeLessThanOrEqualTo, 0) 60 So(h.node.Distance(glist[0]), ShouldNotEqual, h.node.Distance(glist[4])) 61 }) 62 63 Convey("it should only return active gossipers.", t, func() { 64 65 // mark one of nodes previously found as closed 66 id := glist[0] 67 h.node.peerstore.ClearAddrs(id) 68 glist, err = dht.getGossipers() 69 So(err, ShouldBeNil) 70 So(len(glist), ShouldEqual, 5) 71 72 So(glist[0].Pretty(), ShouldNotEqual, id.Pretty()) 73 }) 74 } 75 76 func TestGetFindGossiper(t *testing.T) { 77 d, _, h := PrepareTestChain("test") 78 defer CleanupTestChain(h, d) 79 dht := h.dht 80 Convey("FindGossiper should start empty", t, func() { 81 _, err := dht.FindGossiper() 82 So(err, ShouldEqual, ErrDHTErrNoGossipersAvailable) 83 84 }) 85 86 Convey("AddGossiper of ourselves should not add the gossiper", t, func() { 87 err := dht.AddGossiper(h.node.HashAddr) 88 So(err, ShouldBeNil) 89 _, err = dht.FindGossiper() 90 So(err, ShouldEqual, ErrDHTErrNoGossipersAvailable) 91 }) 92 93 fooAddr, _ := makePeer("peer_foo") 94 addr, err := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/1234") 95 if err != nil { 96 panic(err) 97 } 98 h.node.peerstore.AddAddrs(fooAddr, []ma.Multiaddr{addr}, PeerTTL) 99 100 Convey("AddGossiper add the gossiper", t, func() { 101 err := dht.AddGossiper(fooAddr) 102 So(err, ShouldBeNil) 103 g, err := dht.FindGossiper() 104 So(err, ShouldBeNil) 105 So(g, ShouldEqual, fooAddr) 106 }) 107 108 Convey("DeleteGossiper should remove a gossiper from the database", t, func() { 109 err := dht.DeleteGossiper(fooAddr) 110 So(err, ShouldBeNil) 111 _, err = dht.FindGossiper() 112 So(err, ShouldEqual, ErrDHTErrNoGossipersAvailable) 113 err = dht.DeleteGossiper(fooAddr) 114 So(err.Error(), ShouldEqual, "not found") 115 }) 116 117 Convey("GetGossiper should return the gossiper idx", t, func() { 118 idx, err := dht.GetGossiper(fooAddr) 119 So(err, ShouldBeNil) 120 So(idx, ShouldEqual, 0) 121 }) 122 123 Convey("UpdateGossiper should add a gossiper", t, func() { 124 err := dht.UpdateGossiper(fooAddr, 92) 125 So(err, ShouldBeNil) 126 }) 127 128 Convey("GetGossiper should return the gossiper idx", t, func() { 129 idx, err := dht.GetGossiper(fooAddr) 130 So(err, ShouldBeNil) 131 So(idx, ShouldEqual, 92) 132 }) 133 134 Convey("UpdateGossiper should ignore values less than previously stored", t, func() { 135 err := dht.UpdateGossiper(fooAddr, 32) 136 So(err, ShouldBeNil) 137 idx, err := dht.GetGossiper(fooAddr) 138 So(err, ShouldBeNil) 139 So(idx, ShouldEqual, 92) 140 }) 141 142 Convey("FindGossiper should return the gossiper", t, func() { 143 g, err := dht.FindGossiper() 144 So(err, ShouldBeNil) 145 So(g, ShouldEqual, fooAddr) 146 }) 147 148 Convey("UpdateGossiper should update when value greater than previously stored", t, func() { 149 err := dht.UpdateGossiper(fooAddr, 132) 150 So(err, ShouldBeNil) 151 idx, err := dht.GetGossiper(fooAddr) 152 So(err, ShouldBeNil) 153 So(idx, ShouldEqual, 132) 154 }) 155 156 Convey("GetIdx for self should be 2 to start with (DNA not stored)", t, func() { 157 idx, err := dht.GetIdx() 158 So(err, ShouldBeNil) 159 So(idx, ShouldEqual, 2) 160 }) 161 162 barAddr, _ := makePeer("peer_bar") 163 164 Convey("GetGossiper should return 0 for unknown gossiper", t, func() { 165 idx, err := dht.GetGossiper(barAddr) 166 So(err, ShouldBeNil) 167 So(idx, ShouldEqual, 0) 168 }) 169 } 170 171 func TestGossipData(t *testing.T) { 172 d, _, h := PrepareTestChain("test") 173 defer CleanupTestChain(h, d) 174 dht := h.dht 175 Convey("Idx should be 2 at start (first puts are DNA, Agent & Key but DNA put not stored)", t, func() { 176 var idx int 177 idx, err := dht.GetIdx() 178 So(err, ShouldBeNil) 179 So(idx, ShouldEqual, 2) 180 }) 181 182 var msg1 Message 183 var err error 184 Convey("GetIdxMessage should return the message that made the change", t, func() { 185 msg1, err = dht.GetIdxMessage(1) 186 So(err, ShouldBeNil) 187 So(msg1.Type, ShouldEqual, PUT_REQUEST) 188 So(msg1.Body.(HoldReq).EntryHash.String(), ShouldEqual, h.nodeIDStr) 189 }) 190 191 Convey("GetFingerprint should return the index of the message that made the change", t, func() { 192 f, _ := msg1.Fingerprint() 193 idx, err := dht.GetFingerprint(f) 194 So(err, ShouldBeNil) 195 So(idx, ShouldEqual, 1) 196 197 idx, err = dht.GetFingerprint(NullHash()) 198 So(err, ShouldBeNil) 199 So(idx, ShouldEqual, -1) 200 }) 201 202 // simulate a handled put request 203 now := time.Unix(1, 1) // pick a constant time so the test will always work 204 e := GobEntry{C: "124"} 205 _, hd, _ := h.NewEntry(now, "evenNumbers", &e) 206 hash := hd.EntryLink 207 m1 := h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash}) 208 209 Convey("fingerprints for messages should not exist", t, func() { 210 f, _ := m1.Fingerprint() 211 r, _ := dht.HaveFingerprint(f) 212 So(r, ShouldBeFalse) 213 }) 214 ActionReceiver(h, m1) 215 216 someData := `{"firstName":"Zippy","lastName":"Pinhead"}` 217 e = GobEntry{C: someData} 218 _, hd, _ = h.NewEntry(now, "profile", &e) 219 profileHash := hd.EntryLink 220 221 ee := GobEntry{C: fmt.Sprintf(`{"Links":[{"Base":"%s"},{"Link":"%s"},{"Tag":"4stars"}]}`, hash.String(), profileHash.String())} 222 _, le, _ := h.NewEntry(time.Now(), "rating", &ee) 223 lr := HoldReq{RelatedHash: hash, EntryHash: le.EntryLink} 224 225 m2 := h.node.NewMessage(LINK_REQUEST, lr) 226 ActionReceiver(h, m2) 227 228 Convey("fingerprints for messages should exist", t, func() { 229 f, _ := m1.Fingerprint() 230 r, _ := dht.HaveFingerprint(f) 231 So(r, ShouldBeTrue) 232 f, _ = m1.Fingerprint() 233 r, _ = dht.HaveFingerprint(f) 234 So(r, ShouldBeTrue) 235 }) 236 237 Convey("Idx should be 4 after puts", t, func() { 238 var idx int 239 idx, err := dht.GetIdx() 240 So(err, ShouldBeNil) 241 So(idx, ShouldEqual, 4) 242 }) 243 244 Convey("GetPuts should return a list of the puts since an index value", t, func() { 245 puts, err := dht.GetPuts(0) 246 So(err, ShouldBeNil) 247 So(len(puts), ShouldEqual, 4) 248 So(fmt.Sprintf("%v", puts[2].M), ShouldEqual, fmt.Sprintf("%v", *m1)) 249 So(fmt.Sprintf("%v", puts[3].M), ShouldEqual, fmt.Sprintf("%v", *m2)) 250 So(puts[0].Idx, ShouldEqual, 1) 251 So(puts[1].Idx, ShouldEqual, 2) 252 253 puts, err = dht.GetPuts(4) 254 So(err, ShouldBeNil) 255 So(len(puts), ShouldEqual, 1) 256 So(fmt.Sprintf("%v", puts[0].M), ShouldEqual, fmt.Sprintf("%v", *m2)) 257 So(puts[0].Idx, ShouldEqual, 4) 258 }) 259 } 260 261 func TestGossip(t *testing.T) { 262 nodesCount := 2 263 mt := setupMultiNodeTesting(nodesCount) 264 defer mt.cleanupMultiNodeTesting() 265 nodes := mt.nodes 266 267 h1 := nodes[0] 268 h2 := nodes[1] 269 270 commit(h1, "oddNumbers", "3") 271 commit(h1, "oddNumbers", "5") 272 commit(h1, "oddNumbers", "7") 273 274 puts1, _ := h1.dht.GetPuts(0) 275 puts2, _ := h2.dht.GetPuts(0) 276 277 Convey("Idx after puts", t, func() { 278 So(len(puts1), ShouldEqual, 5) 279 So(len(puts2), ShouldEqual, 2) 280 }) 281 ringConnect(t, mt.ctx, mt.nodes, nodesCount) 282 Convey("gossipWith should add the puts", t, func() { 283 err := h2.dht.gossipWith(h1.nodeID) 284 So(err, ShouldBeNil) 285 go h2.dht.HandleGossipPuts() 286 time.Sleep(time.Millisecond * 100) 287 puts2, _ = h2.dht.GetPuts(0) 288 So(len(puts2), ShouldEqual, 7) 289 }) 290 commit(h1, "evenNumbers", "2") 291 commit(h1, "evenNumbers", "4") 292 293 Convey("gossipWith should add the puts", t, func() { 294 err := h2.dht.gossipWith(h1.nodeID) 295 So(err, ShouldBeNil) 296 go h2.dht.HandleGossipPuts() 297 time.Sleep(time.Millisecond * 100) 298 puts2, _ = h2.dht.GetPuts(0) 299 So(len(puts2), ShouldEqual, 9) 300 }) 301 } 302 303 func TestPeerLists(t *testing.T) { 304 d, _, h := PrepareTestChain("test") 305 defer CleanupTestChain(h, d) 306 307 Convey("it should start with an empty blockedlist", t, func() { 308 peerList, err := h.dht.getList(BlockedList) 309 So(err, ShouldBeNil) 310 So(len(peerList.Records), ShouldEqual, 0) 311 }) 312 313 Convey("it should have peers after they're added", t, func() { 314 pid1, _ := makePeer("testPeer1") 315 pid2, _ := makePeer("testPeer2") 316 pids := []PeerRecord{PeerRecord{ID: pid1}, PeerRecord{ID: pid2}} 317 318 idx, _ := h.dht.GetIdx() 319 err := h.dht.addToList(h.node.NewMessage(LISTADD_REQUEST, ListAddReq{ListType: BlockedList, Peers: []string{peer.IDB58Encode(pid1), peer.IDB58Encode(pid2)}}), PeerList{BlockedList, pids}) 320 So(err, ShouldBeNil) 321 322 afterIdx, _ := h.dht.GetIdx() 323 So(afterIdx-idx, ShouldEqual, 1) 324 325 peerList, err := h.dht.getList(BlockedList) 326 So(err, ShouldBeNil) 327 So(peerList.Type, ShouldEqual, BlockedList) 328 So(len(peerList.Records), ShouldEqual, 2) 329 So(peerList.Records[0].ID, ShouldEqual, pid1) 330 So(peerList.Records[1].ID, ShouldEqual, pid2) 331 }) 332 } 333 334 func TestGossipCycle(t *testing.T) { 335 nodesCount := 2 336 mt := setupMultiNodeTesting(nodesCount) 337 defer mt.cleanupMultiNodeTesting() 338 nodes := mt.nodes 339 h0 := nodes[0] 340 h1 := nodes[1] 341 ringConnect(t, mt.ctx, nodes, nodesCount) 342 343 Convey("the gossip task should schedule a gossipWithRequest", t, func() { 344 So(len(h0.dht.gchan), ShouldEqual, 0) 345 GossipTask(h0) 346 So(len(h0.dht.gchan), ShouldEqual, 1) 347 }) 348 349 Convey("handling the gossipWith should result in getting puts, and a gossip back scheduled on receiving node after a delay", t, func() { 350 351 So(len(h1.dht.gchan), ShouldEqual, 0) 352 So(len(h0.dht.gossipPuts), ShouldEqual, 0) 353 354 x, ok := <-h0.dht.gchan 355 So(ok, ShouldBeTrue) 356 err := handleGossipWith(h0.dht, x) 357 So(err, ShouldBeNil) 358 // we got receivers puts back and scheduled 359 So(len(h0.dht.gossipPuts), ShouldEqual, 2) 360 361 So(len(h1.dht.gchan), ShouldEqual, 0) 362 time.Sleep(GossipBackPutDelay * 3) 363 // gossip back scheduled on receiver after delay 364 So(len(h1.dht.gchan), ShouldEqual, 1) 365 }) 366 367 Convey("gossipWith shouldn't be rentrant with respect to the same gossiper", t, func() { 368 log := &h0.Config.Loggers.Gossip 369 log.color, log.f = log.setupColor("%{message}") 370 371 // if the code were rentrant the log would should the events in a different order 372 ShouldLog(log, func() { 373 go h0.dht.gossipWith(h1.nodeID) 374 h0.dht.gossipWith(h1.nodeID) 375 time.Sleep(time.Millisecond * 100) 376 }, "node0_starting gossipWith <peer.ID UfY4We>\nnode0_no new puts received\nnode0_finish gossipWith <peer.ID UfY4We>, err=<nil>\nnode0_starting gossipWith <peer.ID UfY4We>\nnode0_no new puts received\nnode0_finish gossipWith <peer.ID UfY4We>, err=<nil>\n") 377 log.color, log.f = log.setupColor(log.Format) 378 }) 379 } 380 381 func TestGossipErrorCases(t *testing.T) { 382 nodesCount := 2 383 mt := setupMultiNodeTesting(nodesCount) 384 defer mt.cleanupMultiNodeTesting() 385 nodes := mt.nodes 386 h0 := nodes[0] 387 h1 := nodes[1] 388 ringConnect(t, mt.ctx, nodes, nodesCount) 389 Convey("a rejected put should not break gossiping", t, func() { 390 // inject a bad put 391 hash, _ := NewHash("QmY8Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqz2") 392 h1.dht.Put(h1.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash}), "evenNumbers", hash, h0.nodeID, []byte("bad data"), StatusLive) 393 err := h0.dht.gossipWith(h1.nodeID) 394 So(err, ShouldBeNil) 395 So(len(h0.dht.gossipPuts), ShouldEqual, 3) 396 for i := 0; i < 3; i++ { 397 x, ok := <-h0.dht.gossipPuts 398 So(ok, ShouldBeTrue) 399 err := handleGossipPut(h0.dht, x) 400 So(err, ShouldBeNil) 401 } 402 err = h0.dht.gossipWith(h1.nodeID) 403 So(err, ShouldBeNil) 404 So(len(h0.dht.gossipPuts), ShouldEqual, 0) 405 }) 406 } 407 408 func TestGossipPropagation(t *testing.T) { 409 nodesCount := 10 410 mt := setupMultiNodeTesting(nodesCount) 411 defer mt.cleanupMultiNodeTesting() 412 nodes := mt.nodes 413 ringConnect(t, mt.ctx, nodes, nodesCount) 414 //randConnect(t, mt.ctx, nodes, nodesCount, 7, 4) 415 //starConnect(t, mt.ctx, nodes, nodesCount) 416 Convey("each node should have one gossiper from the ring connect", t, func() { 417 for i := 0; i < nodesCount; i++ { 418 glist, err := nodes[i].dht.getGossipers() 419 So(err, ShouldBeNil) 420 So(len(glist), ShouldEqual, 1) 421 } 422 }) 423 424 Convey("each node should only have it's own puts", t, func() { 425 for i := 0; i < nodesCount; i++ { 426 puts, err := nodes[i].dht.GetPuts(0) 427 So(err, ShouldBeNil) 428 So(len(puts), ShouldEqual, 2) 429 } 430 }) 431 432 Convey("each node should only have everybody's puts after enough propagation time", t, func() { 433 434 for i := 0; i < nodesCount; i++ { 435 nodes[i].Config.gossipInterval = 200 * time.Millisecond 436 nodes[i].StartBackgroundTasks() 437 } 438 439 start := time.Now() 440 propagated := false 441 ticker := time.NewTicker(210 * time.Millisecond) 442 stop := make(chan bool, 1) 443 444 go func() { 445 for tick := range ticker.C { 446 // abort just in case in 4 seconds (only if propgation fails) 447 if tick.Sub(start) > (10 * time.Second) { 448 //fmt.Printf("Aborting!") 449 stop <- true 450 return 451 } 452 453 propagated = true 454 // check to see if the nodes have all gotten the puts yet. 455 for i := 0; i < nodesCount; i++ { 456 puts, _ := nodes[i].dht.GetPuts(0) 457 if len(puts) < nodesCount*2 { 458 propagated = false 459 } 460 /* fmt.Printf("NODE%d(%s): %d:", i, nodes[i].nodeID.Pretty()[2:4], len(puts)) 461 for j := 0; j < len(puts); j++ { 462 f, _ := puts[j].M.Fingerprint() 463 fmt.Printf("%s,", f.String()[2:4]) 464 } 465 fmt.Printf("\n ") 466 nodes[i].dht.glk.RLock() 467 for k, _ := range nodes[i].dht.fingerprints { 468 fmt.Printf("%s,", k) 469 } 470 nodes[i].dht.glk.RUnlock() 471 fmt.Printf("\n ") 472 for k, _ := range nodes[i].dht.sources { 473 fmt.Printf("%d,", findNodeIdx(nodes, k)) 474 } 475 fmt.Printf("\n") 476 */ 477 } 478 if propagated { 479 stop <- true 480 return 481 } 482 // fmt.Printf("\n") 483 } 484 }() 485 <-stop 486 ticker.Stop() 487 So(propagated, ShouldBeTrue) 488 }) 489 } 490 491 func findNodeIdx(nodes []*Holochain, id peer.ID) int { 492 for i, n := range nodes { 493 if id == n.nodeID { 494 return i 495 } 496 } 497 panic("bork!") 498 }