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  }