github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/world_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  	pstore "github.com/libp2p/go-libp2p-peerstore"
     8  	ma "github.com/multiformats/go-multiaddr"
     9  	. "github.com/smartystreets/goconvey/convey"
    10  	"testing"
    11  	"time"
    12  )
    13  
    14  func testAddNodeToWorld(world *World, ID peer.ID, addr ma.Multiaddr) {
    15  	pi := pstore.PeerInfo{ID: ID, Addrs: []ma.Multiaddr{addr}}
    16  	err := world.AddNode(pi, nil)
    17  	if err != nil {
    18  		panic(err)
    19  	}
    20  }
    21  
    22  func testAddNodesToWorld(world *World, start, count int) (nodes []*Node) {
    23  	for i := start; i < start+count; i++ {
    24  		node, err := makeNode(1234, fmt.Sprintf("node%d", i))
    25  		if err != nil {
    26  			panic(err)
    27  		}
    28  		nodes = append(nodes, node)
    29  		testAddNodeToWorld(world, node.HashAddr, node.NetAddr)
    30  	}
    31  	return
    32  }
    33  
    34  func TestWorldNodes(t *testing.T) {
    35  	b58 := "QmY8Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqh2"
    36  	peer, _ := peer.IDB58Decode(b58)
    37  
    38  	ht := BuntHT{}
    39  	world := NewWorld(peer, &ht, nil)
    40  
    41  	Convey("to start with I should know about nobody", t, func() {
    42  		nodes, err := world.AllNodes()
    43  		So(err, ShouldBeNil)
    44  		So(nodes, ShouldBeEmpty)
    45  	})
    46  
    47  	n := testAddNodesToWorld(world, 0, 1)
    48  	Convey("nodes can be added to the world model", t, func() {
    49  		nodes, err := world.AllNodes()
    50  		So(err, ShouldBeNil)
    51  		So(nodes[0], ShouldEqual, n[0].HashAddr)
    52  	})
    53  
    54  	Convey("GetRecord should return the nodes data", t, func() {
    55  		record := world.GetNodeRecord(n[0].HashAddr)
    56  		So(record, ShouldNotBeNil)
    57  		So(record.PeerInfo.ID.Pretty(), ShouldEqual, n[0].HashAddr.Pretty())
    58  		So(len(record.IsHolding), ShouldEqual, 0)
    59  	})
    60  
    61  	hash, _ := NewHash("QmVGtdTZdTFaLsaj2RwdVG8jcjNNcp1DE914DKZ2kHmXHw")
    62  
    63  	Convey("SetNodeHolding should set a node as holding a given hash", t, func() {
    64  		err := world.SetNodeHolding(peer, hash)
    65  		So(err, ShouldEqual, ErrNodeNotFound)
    66  
    67  		theNode := n[0].HashAddr
    68  		holding, err := world.IsHolding(theNode, hash)
    69  		So(err, ShouldBeNil)
    70  		So(holding, ShouldBeFalse)
    71  
    72  		err = world.SetNodeHolding(theNode, hash)
    73  
    74  		holding, err = world.IsHolding(theNode, hash)
    75  		So(err, ShouldBeNil)
    76  		So(holding, ShouldBeTrue)
    77  	})
    78  
    79  	Convey("nodes can be sorted by closeness to a hash", t, func() {
    80  		testAddNodesToWorld(world, 1, 5)
    81  		nodes, err := world.nodesByHash(hash)
    82  		So(err, ShouldBeNil)
    83  		So(len(nodes), ShouldEqual, 7) // 7 because NodesByHash should add in "me" too
    84  		So(distance(nodes[0], hash).Cmp(distance(nodes[1], hash)), ShouldBeLessThanOrEqualTo, 0)
    85  		So(distance(nodes[1], hash).Cmp(distance(nodes[2], hash)), ShouldBeLessThanOrEqualTo, 0)
    86  		So(distance(nodes[2], hash).Cmp(distance(nodes[3], hash)), ShouldBeLessThanOrEqualTo, 0)
    87  		So(distance(nodes[3], hash).Cmp(distance(nodes[4], hash)), ShouldBeLessThanOrEqualTo, 0)
    88  		So(distance(nodes[4], hash).Cmp(distance(nodes[5], hash)), ShouldBeLessThanOrEqualTo, 0)
    89  		So(distance(nodes[6], hash).Cmp(distance(nodes[6], hash)), ShouldBeLessThanOrEqualTo, 0)
    90  	})
    91  
    92  }
    93  
    94  func TestWorldUpdateResponsible(t *testing.T) {
    95  	b58 := "QmY8Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqh2"
    96  	var p1, p2, p3, p4, p5 peer.ID
    97  	var hash1, hash2, hash4 Hash
    98  	p1, _ = peer.IDB58Decode(b58)
    99  	ht := BuntHT{}
   100  	world := NewWorld(p1, &ht, nil)
   101  	var addr ma.Multiaddr
   102  	var err error
   103  	var responsible bool
   104  	testAddNodeToWorld(world, p1, addr)
   105  	Convey("you should always be responsible for yourself!", t, func() {
   106  		hash1 = HashFromPeerID(p1)
   107  		responsible, err := world.UpdateResponsible(hash1, 0)
   108  		So(err, ShouldBeNil)
   109  		So(responsible, ShouldBeTrue)
   110  		responsible, err = world.UpdateResponsible(hash1, 2)
   111  		So(err, ShouldBeNil)
   112  		So(responsible, ShouldBeTrue)
   113  	})
   114  	Convey("you shouldn't be responsible for far away hashes", t, func() {
   115  		p2, _ = peer.IDB58Decode("QmY9Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqh2")
   116  		testAddNodeToWorld(world, p2, addr)
   117  		p3, _ = peer.IDB58Decode("QmY9Mzg9F69e5P9AoQPYbt655HEhc1TVGs11tmfNSzkqh2")
   118  		testAddNodeToWorld(world, p3, addr)
   119  		p4, _ = peer.IDB58Decode("QmY8Mzg9F69e5P9AoQPYbt655HEhc1TVGs11tmfNSykqh2")
   120  		testAddNodeToWorld(world, p4, addr)
   121  
   122  		hash2 = HashFromPeerID(p2)
   123  		responsible, err := world.UpdateResponsible(hash2, 2)
   124  		So(err, ShouldBeNil)
   125  		So(responsible, ShouldBeFalse)
   126  
   127  		hash4 = HashFromPeerID(p4)
   128  		responsible, err = world.UpdateResponsible(hash4, 2)
   129  		So(err, ShouldBeNil)
   130  		So(responsible, ShouldBeTrue)
   131  	})
   132  	Convey("when new closer nodes are added you should stop being responsible", t, func() {
   133  		p5, _ = peer.IDB58Decode("QmY8Mzg9F69e5P9AoQPYbt655HEhc1TVGs11tmfNSykqh1")
   134  		testAddNodeToWorld(world, p5, addr)
   135  
   136  		hash4 = HashFromPeerID(p4)
   137  		responsible, err = world.UpdateResponsible(hash4, 2)
   138  		So(err, ShouldBeNil)
   139  		So(responsible, ShouldBeFalse)
   140  	})
   141  }
   142  
   143  func TestWorldOverlap(t *testing.T) {
   144  	nodesCount := 20
   145  	mt := setupMultiNodeTesting(nodesCount)
   146  	defer mt.cleanupMultiNodeTesting()
   147  	nodes := mt.nodes
   148  	h := nodes[0]
   149  	if !h.Config.EnableWorldModel {
   150  		return
   151  	}
   152  	starConnectMutual(t, mt.ctx, nodes, nodesCount)
   153  
   154  	Convey("it should add all nodes to the world model", t, func() {
   155  		nodes, err := h.world.AllNodes()
   156  		So(err, ShouldBeNil)
   157  		So(len(nodes), ShouldEqual, nodesCount-1)
   158  	})
   159  
   160  	Convey("when redundancy is 0 overlap is 100%", t, func() {
   161  		for i := 0; i < nodesCount; i++ {
   162  			chain := nodes[i].Chain()
   163  			for _, hd := range chain.Headers {
   164  				responsible, err := h.world.UpdateResponsible(hd.EntryLink, 0)
   165  				So(err, ShouldBeNil)
   166  				So(responsible, ShouldBeTrue)
   167  			}
   168  		}
   169  
   170  		entries, err := h.world.Responsible()
   171  		So(err, ShouldBeNil)
   172  		So(len(entries), ShouldEqual, nodesCount+1) // all hashes are agent entries plus the DHA hash
   173  
   174  		for i := 0; i < nodesCount; i++ {
   175  			overlap, err := h.Overlap(nodes[i].AgentHash())
   176  			So(err, ShouldBeNil)
   177  			So(len(overlap), ShouldEqual, nodesCount-1) //all the nodes except me
   178  		}
   179  	})
   180  
   181  	Convey("when redundancy is 5, and assuming no uptime adjustment, overlap should be 4 nodes", t, func() {
   182  		r := 5
   183  		for i := 0; i < nodesCount; i++ {
   184  			nodes[i].nucleus.dna.DHTConfig.RedundancyFactor = r
   185  			chain := nodes[i].Chain()
   186  			for _, hd := range chain.Headers {
   187  				h.world.UpdateResponsible(hd.EntryLink, r)
   188  			}
   189  		}
   190  
   191  		entries, err := h.world.Responsible()
   192  		So(err, ShouldBeNil)
   193  		So(len(entries), ShouldEqual, 2) // I'm only responsible for some of the entries
   194  
   195  		// for all entries there should be 4 other nodes that I hold responsible for it.
   196  		for i := 0; i < len(entries); i++ {
   197  			overlap, err := h.Overlap(entries[i])
   198  			So(err, ShouldBeNil)
   199  			So(len(overlap), ShouldEqual, 4)
   200  		}
   201  	})
   202  }
   203  
   204  func SkipTestWorldHoldingTask(t *testing.T) {
   205  	nodesCount := 10
   206  	mt := setupMultiNodeTesting(nodesCount)
   207  	defer mt.cleanupMultiNodeTesting()
   208  	nodes := mt.nodes
   209  	if !nodes[0].Config.EnableWorldModel {
   210  		return
   211  	}
   212  
   213  	fullConnect(t, mt.ctx, nodes, nodesCount)
   214  	//randConnect(t, mt.ctx, nodes, nodesCount, 7, 4)
   215  	//starConnect(t, mt.ctx, nodes, nodesCount)
   216  	Convey("each node should have one other node from the ring connect", t, func() {
   217  		for i := 0; i < nodesCount; i++ {
   218  			others, err := nodes[i].world.AllNodes()
   219  			So(err, ShouldBeNil)
   220  			So(len(others), ShouldEqual, 1)
   221  		}
   222  	})
   223  
   224  	Convey("each node should only have it's own puts", t, func() {
   225  		for i := 0; i < nodesCount; i++ {
   226  			hashes := myHashes(nodes[i])
   227  			So(len(hashes), ShouldEqual, 3)
   228  		}
   229  	})
   230  
   231  	/*	Convey("HoldingTask should have all sent change requests to all responsible nodes", t, func() {
   232  
   233  		for i := 0; i < nodesCount; i++ {
   234  			nodes[i].nucleus.dna.DHTConfig.RedundancyFactor = 10
   235  
   236  			nodes[i].Config.gossipInterval = 0
   237  			nodes[i].Config.holdingCheckInterval = 0
   238  			nodes[i].StartBackgroundTasks()
   239  		}
   240  
   241  		HoldingTask(nodes[0])
   242  		//		processChangeRequestsInTesting(nodes[0])
   243  		for i := 1; i < nodesCount; i++ {
   244  			//			processChangeRequestsInTesting(nodes[i])
   245  
   246  			puts, err := nodes[i].dht.GetPuts(0)
   247  			So(err, ShouldBeNil)
   248  			fmt.Printf("Puts for %v: %d\n", nodes[i].nodeID.Pretty(), len(puts))
   249  			//	So(len(puts), ShouldEqual, 5)
   250  		}
   251  	})*/
   252  
   253  	Convey("each node should have everybody's puts after enough propigation time", t, func() {
   254  		r := 2
   255  		for i := 0; i < nodesCount; i++ {
   256  			nodes[i].nucleus.dna.DHTConfig.RedundancyFactor = r
   257  			nodes[i].Config.gossipInterval = 0
   258  			nodes[i].Config.holdingCheckInterval = 200 * time.Millisecond
   259  			nodes[i].StartBackgroundTasks()
   260  		}
   261  
   262  		checkPropigated(nodes, r)
   263  
   264  		start := time.Now()
   265  		propigated := false
   266  		ticker := time.NewTicker(210 * time.Millisecond)
   267  		stop := make(chan bool, 1)
   268  
   269  		go func() {
   270  			for tick := range ticker.C {
   271  				// abort just in case in 4 seconds (only if propgation fails)
   272  				if tick.Sub(start) > (10 * time.Second) {
   273  					//fmt.Printf("Aborting!")
   274  					stop <- true
   275  					return
   276  				}
   277  
   278  				propigated = checkPropigated(nodes, r)
   279  				if propigated {
   280  					stop <- true
   281  					return
   282  				}
   283  			}
   284  		}()
   285  		<-stop
   286  		ticker.Stop()
   287  		So(propigated, ShouldBeTrue)
   288  	})
   289  }
   290  
   291  func checkPropigated(nodes []*Holochain, redundancy int) bool {
   292  	nodesCount := len(nodes)
   293  	propigated := true
   294  	allHashes := make(map[Hash]int)
   295  	var required int
   296  	if redundancy == 0 {
   297  		required = nodesCount
   298  	} else {
   299  		required = redundancy
   300  	}
   301  	// check to see if the nodes have all gotten the puts yet.
   302  	for i := 0; i < nodesCount; i++ {
   303  		hashes := myHashes(nodes[i])
   304  		if len(hashes) < nodesCount*2+1 {
   305  			propigated = false
   306  		}
   307  		puts, err := nodes[i].dht.GetPuts(0)
   308  		if err != nil {
   309  			panic(err)
   310  		}
   311  		fmt.Printf("NODE%d(%s):%d- %d:", i, nodes[i].nodeID.Pretty()[2:4], len(puts), len(hashes))
   312  		for j := 0; j < len(hashes); j++ {
   313  			hash := hashes[j]
   314  			//nodes[i].world.UpdateResponsible(hash, r)
   315  
   316  			count, ok := allHashes[hash]
   317  			if !ok {
   318  				allHashes[hash] = 1
   319  			} else {
   320  				allHashes[hash] = count + 1
   321  			}
   322  			fmt.Printf("%s,", hash.String()[2:4])
   323  		}
   324  		fmt.Printf("\n")
   325  
   326  		fmt.Printf("    knows about:")
   327  		others, err := nodes[i].world.AllNodes()
   328  		for j := 0; j < len(others); j++ {
   329  			fmt.Printf("%s,", others[j].Pretty()[2:4])
   330  		}
   331  		fmt.Printf("\n")
   332  
   333  		/*	for j := 0; j < len(hashes); j++ {
   334  			hash := hashes[j]
   335  			fmt.Printf("   RESPONSIBLE for %v:\n       %v\n", hash.String()[2:4], nodes[i].world.responsible[hash])
   336  		}*/
   337  
   338  	}
   339  	fmt.Printf("HashCounts(%d): ", len(allHashes))
   340  	propigated = true
   341  	for hash, count := range allHashes {
   342  		if count < required {
   343  			propigated = false
   344  		}
   345  		fmt.Printf("%s:%d, ", hash.String()[2:4], count)
   346  	}
   347  	fmt.Printf("\n\n")
   348  
   349  	return propigated
   350  }