github.com/metacurrency/holochain@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/dht_test.go (about)

     1  package holochain
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"regexp"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	. "github.com/holochain/holochain-proto/hash"
    13  	b58 "github.com/jbenet/go-base58"
    14  	peer "github.com/libp2p/go-libp2p-peer"
    15  	. "github.com/smartystreets/goconvey/convey"
    16  )
    17  
    18  func TestNewDHT(t *testing.T) {
    19  	d, _, h := PrepareTestChain("test")
    20  	defer CleanupTestChain(h, d)
    21  	os.Remove(filepath.Join(h.DBPath(), DHTStoreFileName))
    22  
    23  	Convey("It should initialize the DHT struct and data store", t, func() {
    24  		So(FileExists(h.DBPath(), DHTStoreFileName), ShouldBeFalse)
    25  		dht := NewDHT(h)
    26  		So(FileExists(h.DBPath(), DHTStoreFileName), ShouldBeTrue)
    27  		So(dht.h, ShouldEqual, h)
    28  		So(dht.config, ShouldEqual, &h.nucleus.dna.DHTConfig)
    29  	})
    30  }
    31  
    32  func TestSetupDHT(t *testing.T) {
    33  	d, _, h := PrepareTestChain("test")
    34  	defer CleanupTestChain(h, d)
    35  
    36  	err := h.dht.SetupDHT()
    37  	Convey("it should add the holochain ID to the DHT", t, func() {
    38  		So(err, ShouldBeNil)
    39  		ID := h.DNAHash()
    40  		So(h.dht.Exists(ID, StatusLive), ShouldBeNil)
    41  		_, et, _, status, err := h.dht.Get(h.dnaHash, StatusLive, GetMaskAll)
    42  		So(err, ShouldBeNil)
    43  		So(status, ShouldEqual, StatusLive)
    44  		So(et, ShouldEqual, DNAEntryType)
    45  
    46  	})
    47  
    48  	Convey("it should push the agent entry to the DHT at genesis time", t, func() {
    49  		data, et, _, status, err := h.dht.Get(h.agentHash, StatusLive, GetMaskAll)
    50  		So(err, ShouldBeNil)
    51  		So(status, ShouldEqual, StatusLive)
    52  		So(et, ShouldEqual, AgentEntryType)
    53  
    54  		var e Entry
    55  		e, _, _ = h.chain.GetEntry(h.agentHash)
    56  
    57  		var b []byte
    58  		b, _ = e.Marshal()
    59  
    60  		So(string(data), ShouldEqual, string(b))
    61  	})
    62  
    63  	Convey("it should push the key to the DHT at genesis time", t, func() {
    64  		keyHash, _ := NewHash(h.nodeIDStr)
    65  		data, et, _, status, err := h.dht.Get(keyHash, StatusLive, GetMaskAll)
    66  		So(err, ShouldBeNil)
    67  		So(status, ShouldEqual, StatusLive)
    68  		So(et, ShouldEqual, KeyEntryType)
    69  		pubKey, err := h.agent.EncodePubKey()
    70  		So(string(data), ShouldEqual, pubKey)
    71  
    72  		data, et, _, status, err = h.dht.Get(keyHash, StatusDefault, GetMaskDefault)
    73  		So(err, ShouldBeNil)
    74  		So(status, ShouldEqual, StatusLive)
    75  
    76  		So(string(data), ShouldEqual, pubKey)
    77  	})
    78  }
    79  
    80  func TestDHTSend(t *testing.T) {
    81  	d, _, h := PrepareTestChain("test")
    82  	defer CleanupTestChain(h, d)
    83  
    84  	hash, _ := NewHash("QmY8Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqh2")
    85  
    86  	Convey("send GET_REQUEST message for non existent hash should get error", t, func() {
    87  		msg := h.node.NewMessage(GET_REQUEST, GetReq{H: hash, StatusMask: StatusLive})
    88  		_, err := h.dht.send(nil, h.node.HashAddr, msg)
    89  		So(err, ShouldEqual, ErrHashNotFound)
    90  	})
    91  
    92  	now := time.Unix(1, 1) // pick a constant time so the test will always work
    93  	e := GobEntry{C: "4"}
    94  	_, hd, err := h.NewEntry(now, "evenNumbers", &e)
    95  	if err != nil {
    96  		panic(err)
    97  	}
    98  
    99  	// publish the entry data to the dht
   100  	hash = hd.EntryLink
   101  	Convey("after a handled PUT_REQUEST data should be stored in DHT", t, func() {
   102  		msg := h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash})
   103  		r, err := h.dht.send(nil, h.node.HashAddr, msg)
   104  		So(err, ShouldBeNil)
   105  		So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   106  		hd, _ := h.chain.GetEntryHeader(hash)
   107  		So(hd.EntryLink.Equal(hash), ShouldBeTrue)
   108  	})
   109  
   110  	Convey("send GET_REQUEST message should return content", t, func() {
   111  		msg := h.node.NewMessage(GET_REQUEST, GetReq{H: hash, StatusMask: StatusLive})
   112  		r, err := h.dht.send(nil, h.node.HashAddr, msg)
   113  		So(err, ShouldBeNil)
   114  		resp := r.(GetResp)
   115  		So(fmt.Sprintf("%v", resp.Entry), ShouldEqual, fmt.Sprintf("%v", e))
   116  	})
   117  
   118  	Convey("send GET_REQUEST message should return content of sys types", t, func() {
   119  		msg := h.node.NewMessage(GET_REQUEST, GetReq{H: h.agentHash, StatusMask: StatusLive})
   120  		r, err := h.dht.send(nil, h.nodeID, msg)
   121  		So(err, ShouldBeNil)
   122  		resp := r.(GetResp)
   123  		ae, _ := h.agent.AgentEntry(nil)
   124  		a, _ := ae.ToJSON()
   125  		So(resp.Entry.Content().(string), ShouldEqual, a)
   126  
   127  		msg = h.node.NewMessage(GET_REQUEST, GetReq{H: HashFromPeerID(h.nodeID), StatusMask: StatusLive})
   128  		r, err = h.dht.send(nil, h.nodeID, msg)
   129  		So(err, ShouldBeNil)
   130  		resp = r.(GetResp)
   131  		So(resp.Entry.Content().(string), ShouldEqual, ae.PublicKey)
   132  
   133  		// for now this is an error because we presume everyone has the DNA.
   134  		// once we implement dna changes, this needs to be changed
   135  		msg = h.node.NewMessage(GET_REQUEST, GetReq{H: h.dnaHash, StatusMask: StatusLive})
   136  		r, err = h.dht.send(nil, h.nodeID, msg)
   137  		So(err, ShouldBeError)
   138  
   139  	})
   140  }
   141  
   142  func TestDHTQueryGet(t *testing.T) {
   143  	nodesCount := 6
   144  	mt := setupMultiNodeTesting(nodesCount)
   145  	defer mt.cleanupMultiNodeTesting()
   146  
   147  	h := mt.nodes[0]
   148  
   149  	now := time.Unix(1, 1) // pick a constant time so the test will always work
   150  	e := GobEntry{C: "4"}
   151  	_, hd, err := h.NewEntry(now, "evenNumbers", &e)
   152  	if err != nil {
   153  		panic(err)
   154  	}
   155  
   156  	/*for i := 0; i < nodesCount; i++ {
   157  		fmt.Printf("node%d:%v\n", i, mt.nodes[i].node.HashAddr.Pretty()[2:6])
   158  	}*/
   159  
   160  	// publish the entry data to local DHT node (0)
   161  	hash := hd.EntryLink
   162  	msg := h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash})
   163  	_, err = h.dht.send(nil, h.node.HashAddr, msg)
   164  	if err != nil {
   165  		panic(err)
   166  	}
   167  
   168  	ringConnect(t, mt.ctx, mt.nodes, nodesCount)
   169  
   170  	// pick a distant node that has to do some of the recursive lookups to get back to node 0.
   171  	Convey("Kademlia GET_REQUEST should return content", t, func() {
   172  		h2 := mt.nodes[nodesCount-2]
   173  		r, err := h2.dht.Query(hash, GET_REQUEST, GetReq{H: hash, StatusMask: StatusLive})
   174  		So(err, ShouldBeNil)
   175  		resp := r.(GetResp)
   176  		So(fmt.Sprintf("%v", resp.Entry), ShouldEqual, fmt.Sprintf("%v", e))
   177  	})
   178  }
   179  
   180  func TestDHTKadPut(t *testing.T) {
   181  	nodesCount := 6
   182  	mt := setupMultiNodeTesting(nodesCount)
   183  	defer mt.cleanupMultiNodeTesting()
   184  
   185  	h := mt.nodes[0]
   186  
   187  	now := time.Unix(1, 1) // pick a constant time so the test will always work
   188  	e := GobEntry{C: "4"}
   189  	_, hd, err := h.NewEntry(now, "evenNumbers", &e)
   190  	if err != nil {
   191  		panic(err)
   192  	}
   193  	hash := hd.EntryLink
   194  
   195  	/*
   196  		for i := 0; i < nodesCount; i++ {
   197  			fmt.Printf("node%d:%v\n", i, mt.nodes[i].node.HashAddr.Pretty()[2:6])
   198  		}
   199  		//node0:NnRV
   200  		//node1:UfY4
   201  		//node2:YA62
   202  		//node3:S4BF
   203  		//node4:W4He
   204  		//node5:dxxu
   205  
   206  		starConnect(t, mt.ctx, mt.nodes, nodesCount)
   207  		// get closest peers in the routing table
   208  		rtp := h.node.routingTable.NearestPeers(hash, AlphaValue)
   209  		fmt.Printf("CLOSE:%v\n", rtp)
   210  
   211  		//[<peer.ID S4BFeT> <peer.ID W4HeEG> <peer.ID UfY4We>]
   212  		//i.e 3,4,1
   213  	*/
   214  
   215  	ringConnect(t, mt.ctx, mt.nodes, nodesCount)
   216  
   217  	Convey("Kademlia PUT_REQUEST should put the hash to its closet node even if we don't know about it yet", t, func() {
   218  
   219  		rtp := h.node.routingTable.NearestPeers(hash, AlphaValue)
   220  		// check that our routing table doesn't contain closest node yet
   221  		So(fmt.Sprintf("%v", rtp), ShouldEqual, "[<peer.ID UfY4We> <peer.ID dxxuES>]")
   222  		err := h.dht.Change(hash, PUT_REQUEST, HoldReq{EntryHash: hash})
   223  		So(err, ShouldBeNil)
   224  
   225  		processChangeRequestsInTesting(h)
   226  		rtp = h.node.routingTable.NearestPeers(hash, AlphaValue)
   227  		// routing table should be updated
   228  		So(fmt.Sprintf("%v", rtp), ShouldEqual, "[<peer.ID S4BFeT> <peer.ID W4HeEG> <peer.ID UfY4We>]")
   229  		// and get from node should get the value
   230  		msg := h.node.NewMessage(GET_REQUEST, GetReq{H: hash, StatusMask: StatusLive})
   231  		r, err := h.dht.send(nil, mt.nodes[3].nodeID, msg)
   232  		So(err, ShouldBeNil)
   233  		resp := r.(GetResp)
   234  		So(fmt.Sprintf("%v", resp.Entry), ShouldEqual, fmt.Sprintf("%v", e))
   235  
   236  		if h.Config.EnableWorldModel {
   237  			// and the world model should show that it's being held
   238  			holding, err := h.world.IsHolding(mt.nodes[3].nodeID, hash)
   239  			So(err, ShouldBeNil)
   240  			So(holding, ShouldBeTrue)
   241  		}
   242  	})
   243  }
   244  
   245  func TestActionReceiver(t *testing.T) {
   246  	d, _, h := PrepareTestChain("test")
   247  	defer CleanupTestChain(h, d)
   248  
   249  	Convey("PUT_REQUEST should fail if body isn't a hash", t, func() {
   250  		m := h.node.NewMessage(PUT_REQUEST, "foo")
   251  		_, err := ActionReceiver(h, m)
   252  		So(err.Error(), ShouldEqual, "Unexpected request body type 'string' in put request, expecting holochain.HoldReq")
   253  	})
   254  
   255  	Convey("LINK_REQUEST should fail if body not a good linking request", t, func() {
   256  		m := h.node.NewMessage(LINK_REQUEST, "foo")
   257  		_, err := ActionReceiver(h, m)
   258  		So(err.Error(), ShouldEqual, "Unexpected request body type 'string' in link request, expecting holochain.HoldReq")
   259  	})
   260  
   261  	now := time.Unix(1, 1) // pick a constant time so the test will always work
   262  	e := GobEntry{C: "124"}
   263  	_, hd, _ := h.NewEntry(now, "evenNumbers", &e)
   264  	hash := hd.EntryLink
   265  
   266  	Convey("PUT_REQUEST should queue a valid message", t, func() {
   267  		m := h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash})
   268  		r, err := ActionReceiver(h, m)
   269  		So(err, ShouldBeNil)
   270  		So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   271  		data, _ := MakeReceiptData(m, ReceiptOK)
   272  		matches, err := h.VerifySignature(r.(HoldResp).Signature, string(data), h.agent.PubKey())
   273  		So(err, ShouldBeNil)
   274  		So(matches, ShouldBeTrue)
   275  	})
   276  
   277  	Convey("GET_REQUEST should return the requested values", t, func() {
   278  		m := h.node.NewMessage(GET_REQUEST, GetReq{H: hash, StatusMask: StatusLive})
   279  		r, err := ActionReceiver(h, m)
   280  		So(err, ShouldBeNil)
   281  		resp := r.(GetResp)
   282  		So(fmt.Sprintf("%v", resp.Entry), ShouldEqual, fmt.Sprintf("%v", e))
   283  
   284  		m = h.node.NewMessage(GET_REQUEST, GetReq{H: hash, GetMask: GetMaskEntryType})
   285  		r, err = ActionReceiver(h, m)
   286  		So(err, ShouldBeNil)
   287  		resp = r.(GetResp)
   288  		So(resp.Entry.C, ShouldEqual, nil)
   289  		So(resp.EntryType, ShouldEqual, "evenNumbers")
   290  
   291  		m = h.node.NewMessage(GET_REQUEST, GetReq{H: hash, GetMask: GetMaskEntry + GetMaskEntryType})
   292  		r, err = ActionReceiver(h, m)
   293  		So(err, ShouldBeNil)
   294  		resp = r.(GetResp)
   295  		So(fmt.Sprintf("%v", resp.Entry), ShouldEqual, fmt.Sprintf("%v", e))
   296  		So(resp.EntryType, ShouldEqual, "evenNumbers")
   297  
   298  		m = h.node.NewMessage(GET_REQUEST, GetReq{H: hash, GetMask: GetMaskSources})
   299  		r, err = ActionReceiver(h, m)
   300  		So(err, ShouldBeNil)
   301  		resp = r.(GetResp)
   302  		So(resp.Entry.C, ShouldEqual, nil)
   303  		So(fmt.Sprintf("%v", resp.Sources), ShouldEqual, fmt.Sprintf("[%v]", h.nodeIDStr))
   304  		So(resp.EntryType, ShouldEqual, "evenNumbers") // you allways get the entry type even if not in getmask at this level (ActionReceiver) because we have to be able to look up the definition to interpret the contents
   305  
   306  		m = h.node.NewMessage(GET_REQUEST, GetReq{H: hash, GetMask: GetMaskEntry + GetMaskSources})
   307  		r, err = ActionReceiver(h, m)
   308  		So(err, ShouldBeNil)
   309  		resp = r.(GetResp)
   310  		So(fmt.Sprintf("%v", resp.Entry), ShouldEqual, fmt.Sprintf("%v", e))
   311  		So(fmt.Sprintf("%v", resp.Sources), ShouldEqual, fmt.Sprintf("[%v]", h.nodeIDStr))
   312  		So(resp.EntryType, ShouldEqual, "evenNumbers") // you allways get the entry type even if not in getmask at this level (ActionReceiver) because we have to be able to look up the definition to interpret the contents
   313  	})
   314  
   315  	someData := `{"firstName":"Zippy","lastName":"Pinhead"}`
   316  	e = GobEntry{C: someData}
   317  	_, hd, _ = h.NewEntry(now, "profile", &e)
   318  	profileHash := hd.EntryLink
   319  
   320  	le := GobEntry{C: fmt.Sprintf(`{"Links":[{"Base":"%s","Link":"%s","Tag":"4stars"},{"Base":"%s","Link":"%s","Tag":"3stars"}]}`, hash.String(), profileHash.String(), hash.String(), h.agentHash)}
   321  	_, lhd, _ := h.NewEntry(time.Now(), "rating", &le)
   322  
   323  	Convey("LINK_REQUEST should store links", t, func() {
   324  		lr := HoldReq{RelatedHash: hash, EntryHash: lhd.EntryLink}
   325  		m := h.node.NewMessage(LINK_REQUEST, lr)
   326  		r, err := ActionReceiver(h, m)
   327  		So(err, ShouldBeNil)
   328  		So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   329  		data, _ := MakeReceiptData(m, ReceiptOK)
   330  		matches, err := h.VerifySignature(r.(HoldResp).Signature, string(data), h.agent.PubKey())
   331  		So(err, ShouldBeNil)
   332  		So(matches, ShouldBeTrue)
   333  
   334  		// check that it got put
   335  		meta, err := h.dht.GetLinks(hash, "4stars", StatusLive)
   336  		So(err, ShouldBeNil)
   337  		So(meta[0].H, ShouldEqual, hd.EntryLink.String())
   338  	})
   339  
   340  	e2 := GobEntry{C: "322"}
   341  	hash2, _ := e2.Sum(h.hashSpec)
   342  
   343  	e3 := GobEntry{C: "324"}
   344  	hash3, _ := e3.Sum(h.hashSpec)
   345  
   346  	Convey("LINK_REQUEST of unknown hash should get queued for retry", t, func() {
   347  		lr := HoldReq{RelatedHash: hash2, EntryHash: hash3}
   348  		m := h.node.NewMessage(LINK_REQUEST, lr)
   349  		r, err := ActionReceiver(h, m)
   350  		So(err, ShouldBeNil)
   351  		So(r, ShouldEqual, DHTChangeUnknownHashQueuedForRetry)
   352  		So(len(h.dht.retryQueue), ShouldEqual, 1)
   353  		<-h.dht.retryQueue // unload the queue
   354  	})
   355  
   356  	Convey("GETLINK_REQUEST should retrieve link values", t, func() {
   357  		mq := LinkQuery{Base: hash, T: "4stars"}
   358  		m := h.node.NewMessage(GETLINK_REQUEST, mq)
   359  		r, err := ActionReceiver(h, m)
   360  		So(err, ShouldBeNil)
   361  		results := r.(*LinkQueryResp)
   362  		So(results.Links[0].H, ShouldEqual, hd.EntryLink.String())
   363  		So(results.Links[0].T, ShouldEqual, "")
   364  	})
   365  
   366  	Convey("GETLINK_REQUEST with empty tag should retrieve all linked values", t, func() {
   367  		mq := LinkQuery{Base: hash, T: ""}
   368  		m := h.node.NewMessage(GETLINK_REQUEST, mq)
   369  		r, err := ActionReceiver(h, m)
   370  		So(err, ShouldBeNil)
   371  		results := r.(*LinkQueryResp)
   372  		var l4star, l3star TaggedHash
   373  		// could come back in any order...
   374  		if results.Links[0].T == "4stars" {
   375  			l4star = results.Links[0]
   376  			l3star = results.Links[1]
   377  
   378  		} else {
   379  			l4star = results.Links[1]
   380  			l3star = results.Links[0]
   381  		}
   382  		So(l3star.H, ShouldEqual, h.agentHash.String())
   383  		So(l3star.T, ShouldEqual, "3stars")
   384  		So(l4star.H, ShouldEqual, hd.EntryLink.String())
   385  		So(l4star.T, ShouldEqual, "4stars")
   386  	})
   387  
   388  	Convey("GOSSIP_REQUEST should request and advertise data by idx", t, func() {
   389  		g := GossipReq{MyIdx: 1, YourIdx: 2}
   390  		m := h.node.NewMessage(GOSSIP_REQUEST, g)
   391  		r, err := GossipReceiver(h, m)
   392  		So(err, ShouldBeNil)
   393  		gr := r.(Gossip)
   394  		So(len(gr.Puts), ShouldEqual, 4)
   395  	})
   396  
   397  	le2 := GobEntry{C: fmt.Sprintf(`{"Links":[{"Base":"%s","Link":"%s","Tag":"4stars","LinkAction":"%s"}]}`, hash.String(), profileHash.String(), DelLinkAction)}
   398  	_, lhd2, _ := h.NewEntry(time.Now(), "rating", &le2)
   399  
   400  	Convey("LINK_REQUEST with del type should mark a link as deleted", t, func() {
   401  		lr := HoldReq{RelatedHash: hash, EntryHash: lhd2.EntryLink}
   402  		m := h.node.NewMessage(LINK_REQUEST, lr)
   403  		r, err := ActionReceiver(h, m)
   404  		So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   405  		data, _ := MakeReceiptData(m, ReceiptOK)
   406  		matches, err := h.VerifySignature(r.(HoldResp).Signature, string(data), h.agent.PubKey())
   407  		So(err, ShouldBeNil)
   408  		So(matches, ShouldBeTrue)
   409  
   410  		results, err := h.dht.GetLinks(hash, "4stars", StatusLive)
   411  		So(err, ShouldBeNil)
   412  		So(len(results), ShouldEqual, 0)
   413  
   414  		results, err = h.dht.GetLinks(hash, "4stars", StatusDeleted)
   415  		So(err, ShouldBeNil)
   416  		So(len(results), ShouldEqual, 1)
   417  	})
   418  
   419  	Convey("GETLINK_REQUEST with mask option should retrieve deleted link values", t, func() {
   420  		mq := LinkQuery{Base: hash, T: "4stars", StatusMask: StatusDeleted}
   421  		m := h.node.NewMessage(GETLINK_REQUEST, mq)
   422  		r, err := ActionReceiver(h, m)
   423  		So(err, ShouldBeNil)
   424  		results := r.(*LinkQueryResp)
   425  		So(results.Links[0].H, ShouldEqual, hd.EntryLink.String())
   426  	})
   427  
   428  	Convey("MOD_REQUEST of unknown hash should get queued for retry", t, func() {
   429  		req := HoldReq{RelatedHash: hash2, EntryHash: hash3}
   430  		m := h.node.NewMessage(MOD_REQUEST, req)
   431  		r, err := ActionReceiver(h, m)
   432  		So(err, ShouldBeNil)
   433  		So(r, ShouldEqual, DHTChangeUnknownHashQueuedForRetry)
   434  		So(len(h.dht.retryQueue), ShouldEqual, 1)
   435  		<-h.dht.retryQueue // unload the queue
   436  	})
   437  
   438  	// put a second entry to DHT
   439  	h.NewEntry(now, "evenNumbers", &e2)
   440  	m2 := h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash2})
   441  	ActionReceiver(h, m2)
   442  
   443  	Convey("MOD_REQUEST should set hash to modified", t, func() {
   444  		req := HoldReq{RelatedHash: hash, EntryHash: hash2}
   445  		m := h.node.NewMessage(MOD_REQUEST, req)
   446  		r, err := ActionReceiver(h, m)
   447  		So(err, ShouldBeNil)
   448  		So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   449  		data, _ := MakeReceiptData(m, ReceiptOK)
   450  		matches, err := h.VerifySignature(r.(HoldResp).Signature, string(data), h.agent.PubKey())
   451  		So(err, ShouldBeNil)
   452  		So(matches, ShouldBeTrue)
   453  	})
   454  
   455  	Convey("DELETE_REQUEST should set status of hash to deleted", t, func() {
   456  		entry := DelEntry{Hash: hash2, Message: "expired"}
   457  		a := NewDelAction(entry)
   458  		_, err := h.doCommit(a, NullHash())
   459  		entryHash := a.header.EntryLink
   460  		m := h.node.NewMessage(DEL_REQUEST, HoldReq{RelatedHash: hash2, EntryHash: entryHash})
   461  		r, err := ActionReceiver(h, m)
   462  		So(err, ShouldBeNil)
   463  		So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   464  		data, _ := MakeReceiptData(m, ReceiptOK)
   465  		matches, err := h.VerifySignature(r.(HoldResp).Signature, string(data), h.agent.PubKey())
   466  		So(err, ShouldBeNil)
   467  		So(matches, ShouldBeTrue)
   468  
   469  		data, entryType, _, status, _ := h.dht.Get(hash2, StatusAny, GetMaskAll)
   470  		var e GobEntry
   471  		e.Unmarshal(data)
   472  		So(e.C, ShouldEqual, "322")
   473  		So(entryType, ShouldEqual, "evenNumbers")
   474  		So(status, ShouldEqual, StatusDeleted)
   475  	})
   476  
   477  	Convey("DELETE_REQUEST of unknown hash should get queued for retry", t, func() {
   478  		req := HoldReq{RelatedHash: hash3, EntryHash: hash3}
   479  		m := h.node.NewMessage(DEL_REQUEST, req)
   480  		r, err := ActionReceiver(h, m)
   481  		So(err, ShouldBeNil)
   482  		So(r, ShouldEqual, DHTChangeUnknownHashQueuedForRetry)
   483  		So(len(h.dht.retryQueue), ShouldEqual, 1)
   484  	})
   485  
   486  	Convey("LISTADD_REQUEST with bad warrant should return error", t, func() {
   487  		pid, _ := makePeer("testPeer")
   488  		m := h.node.NewMessage(LISTADD_REQUEST,
   489  			ListAddReq{
   490  				ListType:    BlockedList,
   491  				Peers:       []string{peer.IDB58Encode(pid)},
   492  				WarrantType: SelfRevocationType,
   493  				Warrant:     []byte("bad warrant!"),
   494  			})
   495  		_, err := ActionReceiver(h, m)
   496  		So(err.Error(), ShouldEqual, "List add request rejected on warrant failure: unable to decode warrant (invalid character 'b' looking for beginning of value)")
   497  	})
   498  
   499  	Convey("LISTADD_REQUEST with warrant out of context should return error", t, func() {
   500  		pid, oldPrivKey := makePeer("testPeer")
   501  		_, newPrivKey := makePeer("peer1")
   502  		revocation, _ := NewSelfRevocation(oldPrivKey, newPrivKey, []byte("extra data"))
   503  		w, _ := NewSelfRevocationWarrant(revocation)
   504  		data, _ := w.Encode()
   505  		m := h.node.NewMessage(LISTADD_REQUEST,
   506  			ListAddReq{
   507  				ListType:    BlockedList,
   508  				Peers:       []string{peer.IDB58Encode(pid)},
   509  				WarrantType: SelfRevocationType,
   510  				Warrant:     data,
   511  			})
   512  		_, err := ActionReceiver(h, m)
   513  		So(err.Error(), ShouldEqual, "List add request rejected on warrant failure: expected old key to be modified on DHT")
   514  
   515  	})
   516  
   517  	/*
   518  			getting a good warrant without also having already had the addToList happen is hard,
   519  			 so not quite sure how to test this
   520  					Convey("LISTADD_REQUEST with good warrant should add to list", t, func() {
   521  						pid, oldPrivKey := makePeer("testPeer")
   522  						_, newPrivKey := makePeer("peer1")
   523  						revocation, _ := NewSelfRevocation(oldPrivKey, newPrivKey, []byte("extra data"))
   524  						w, _ := NewSelfRevocationWarrant(revocation)
   525  						data, _ := w.Encode()
   526  						m := h.node.NewMessage(LISTADD_REQUEST,
   527  							ListAddReq{
   528  								ListType:    BlockedList,
   529  								Peers:       []string{peer.IDB58Encode(pid)},
   530  								WarrantType: SelfRevocationType,
   531  								Warrant:     data,
   532  							})
   533  						r, err := ActionReceiver(h, m)
   534  						So(err, ShouldBeNil)
   535  		                           	So(r.(HoldResp).Code, ShouldEqual, ReceiptOK)
   536  
   537  						peerList, err := h.dht.getList(BlockedList)
   538  						So(err, ShouldBeNil)
   539  						So(len(peerList.Records), ShouldEqual, 1)
   540  						So(peerList.Records[0].ID, ShouldEqual, pid)
   541  					})
   542  	*/
   543  
   544  }
   545  
   546  func TestDHTDump(t *testing.T) {
   547  	d, _, h := PrepareTestChain("test")
   548  	defer CleanupTestChain(h, d)
   549  
   550  	ht := h.dht.ht.(*BuntHT)
   551  	Convey("dht dump of index 1 should show the agent put", t, func() {
   552  		msg, _ := ht.GetIdxMessage(1)
   553  		f, _ := msg.Fingerprint()
   554  		msgStr := msg.String()
   555  
   556  		str, err := ht.dumpIdx(1)
   557  		So(err, ShouldBeNil)
   558  
   559  		So(strings.Index(str, fmt.Sprintf("MSG (fingerprint %v)", f)) >= 0, ShouldBeTrue)
   560  		So(strings.Index(str, msgStr) >= 0, ShouldBeTrue)
   561  	})
   562  
   563  	Convey("dht dump of index 99 should return err", t, func() {
   564  		_, err := ht.dumpIdx(99)
   565  		So(err.Error(), ShouldEqual, "no such change index")
   566  	})
   567  
   568  	reviewHash := commit(h, "review", "this is my bogus review of the user")
   569  	commit(h, "rating", fmt.Sprintf(`{"Links":[{"Base":"%s","Link":"%s","Tag":"4stars"}]}`, h.nodeIDStr, reviewHash.String()))
   570  
   571  	Convey("dht.String() should produce human readable DHT", t, func() {
   572  		dump := h.dht.String()
   573  		So(dump, ShouldContainSubstring, "DHT changes: 5")
   574  		d, _ := ht.dumpIdx(1)
   575  		So(dump, ShouldContainSubstring, d)
   576  		d, _ = ht.dumpIdx(2)
   577  		So(dump, ShouldContainSubstring, d)
   578  
   579  		So(dump, ShouldContainSubstring, "DHT entries:")
   580  		So(dump, ShouldContainSubstring, fmt.Sprintf("Hash--%s (status 1)", h.nodeIDStr))
   581  		pk, _ := h.agent.PubKey().Bytes()
   582  		So(dump, ShouldContainSubstring, fmt.Sprintf("Value: %s", string(b58.Encode(pk))))
   583  		So(dump, ShouldContainSubstring, fmt.Sprintf("Sources: %s", h.nodeIDStr))
   584  
   585  		So(dump, ShouldContainSubstring, fmt.Sprintf("Linked to: %s with tag %s", reviewHash, "4stars"))
   586  	})
   587  
   588  	Convey("dht.JSON() should output DHT formatted as JSON string", t, func() {
   589  		dump, err := h.dht.JSON()
   590  		So(err, ShouldBeNil)
   591  		d, _ := ht.dumpIdxJSON(1)
   592  		So(NormaliseJSON(dump), ShouldContainSubstring, NormaliseJSON(d))
   593  		d, _ = ht.dumpIdxJSON(2)
   594  		So(NormaliseJSON(dump), ShouldContainSubstring, NormaliseJSON(d))
   595  
   596  		json := NormaliseJSON(dump)
   597  		matched, err := regexp.MatchString(`{"dht_changes":\[.*\],"dht_entries":\[.*\]}`, json)
   598  		So(err, ShouldBeNil)
   599  		So(matched, ShouldBeTrue)
   600  	})
   601  }
   602  
   603  func TestDHTRetry(t *testing.T) {
   604  	d, _, h := PrepareTestChain("test")
   605  	defer CleanupTestChain(h, d)
   606  
   607  	d1 := `{"firstName":"Zippy","lastName":"Pinhead"}`
   608  	e := GobEntry{C: d1}
   609  	hash, _ := e.Sum(h.hashSpec)
   610  	d2 := `{"firstName":"Zerbina","lastName":"Pinhead"}`
   611  	e2 := GobEntry{C: d2}
   612  	hash2, _ := e2.Sum(h.hashSpec)
   613  
   614  	Convey("it should make a change after some retries", t, func() {
   615  		req := HoldReq{RelatedHash: hash, EntryHash: hash2}
   616  		m := h.node.NewMessage(MOD_REQUEST, req)
   617  		r, err := ActionReceiver(h, m)
   618  		So(err, ShouldBeNil)
   619  		So(r, ShouldEqual, DHTChangeUnknownHashQueuedForRetry)
   620  
   621  		// pause for a few retires
   622  		h.node.stoppers[RetryingStopper] = h.TaskTicker(time.Millisecond*10, RetryTask)
   623  		time.Sleep(time.Millisecond * 25)
   624  
   625  		// add the entries and get them into the DHT
   626  		h.NewEntry(time.Now(), "profile", &e)
   627  		h.NewEntry(time.Now(), "profile", &e2)
   628  		m = h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash})
   629  		err = h.dht.Put(m, "profile", hash, h.nodeID, []byte(d1), StatusLive)
   630  		So(err, ShouldBeNil)
   631  		m = h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash2})
   632  		err = h.dht.Put(m, "profile", hash2, h.nodeID, []byte(d2), StatusLive)
   633  		So(err, ShouldBeNil)
   634  
   635  		_, _, _, status, _ := h.dht.Get(hash, StatusAny, GetMaskAll)
   636  		So(status, ShouldEqual, StatusLive)
   637  
   638  		// wait for next retry
   639  		time.Sleep(time.Millisecond * 40)
   640  
   641  		_, _, _, status, _ = h.dht.Get(hash, StatusAny, GetMaskAll)
   642  		So(status, ShouldEqual, StatusModified)
   643  
   644  		// stop retrying for next test
   645  		stop := h.node.stoppers[RetryingStopper]
   646  		h.node.stoppers[RetryingStopper] = nil
   647  		stop <- true
   648  
   649  	})
   650  
   651  	Convey("retries should be limited", t, func() {
   652  		e3 := GobEntry{C: `{"firstName":"Zappy","lastName":"Pinhead"}`}
   653  		hash3, _ := e3.Sum(h.hashSpec)
   654  		e4 := GobEntry{C: `{"firstName":"Zuppy","lastName":"Pinhead"}`}
   655  		hash4, _ := e4.Sum(h.hashSpec)
   656  		req := HoldReq{RelatedHash: hash3, EntryHash: hash4}
   657  		m := h.node.NewMessage(MOD_REQUEST, req)
   658  		r, err := ActionReceiver(h, m)
   659  		So(err, ShouldBeNil)
   660  		So(r, ShouldEqual, DHTChangeUnknownHashQueuedForRetry)
   661  		So(len(h.dht.retryQueue), ShouldEqual, 1)
   662  
   663  		interval := time.Millisecond * 10
   664  		h.node.stoppers[RetryingStopper] = h.TaskTicker(interval, RetryTask)
   665  		time.Sleep(interval * (MaxRetries + 2))
   666  		So(len(h.dht.retryQueue), ShouldEqual, 0)
   667  	})
   668  }
   669  
   670  func TestDHTMultiNode(t *testing.T) {
   671  	nodesCount := 10
   672  	mt := setupMultiNodeTesting(nodesCount)
   673  	defer mt.cleanupMultiNodeTesting()
   674  	nodes := mt.nodes
   675  
   676  	ringConnectMutual(t, mt.ctx, mt.nodes, nodesCount)
   677  	var connections int
   678  	Convey("each node should be able to get the key of others", t, func() {
   679  		for i := 0; i < nodesCount; i++ {
   680  			h1 := nodes[i]
   681  			for j := 0; j < nodesCount; j++ {
   682  				h2 := nodes[j]
   683  				options := GetOptions{StatusMask: StatusDefault}
   684  				req := GetReq{H: HashFromPeerID(h1.nodeID), StatusMask: options.StatusMask, GetMask: options.GetMask}
   685  				response, err := callGet(h2, req, &options)
   686  				if err != nil {
   687  					fmt.Printf("FAIL   : %v couldn't get from %v err: %err\n", h2.nodeID, h1.nodeID, err)
   688  				} else {
   689  					e := response.(GetResp).Entry
   690  					responseStr := fmt.Sprintf("%v", e.Content())
   691  					pk, _ := h1.agent.EncodePubKey()
   692  					expectedResponseStr := pk
   693  					if responseStr == expectedResponseStr {
   694  						connections += 1
   695  						//fmt.Printf("SUCCESS: %v got from          %v\n", h2.nodeID, h1.nodeID)
   696  					} else {
   697  						//fmt.Printf("Expected:%s\n", expectedResponseStr)
   698  						//fmt.Printf("Got     :%s\n", responseStr)
   699  					}
   700  				}
   701  			}
   702  		}
   703  		So(connections, ShouldEqual, nodesCount*nodesCount)
   704  	})
   705  
   706  	hashes := []Hash{}
   707  	// add a bunch of data and links to that data on the key
   708  	for i := 0; i < nodesCount; i++ {
   709  		h := nodes[i]
   710  		hash := commit(h, "review", fmt.Sprintf("this statement by node %d (%v)", i, h.nodeID))
   711  		commit(h, "rating", fmt.Sprintf(`{"Links":[{"Base":"%s","Link":"%s","Tag":"statement"}]}`, h.nodeIDStr, hash.String()))
   712  		hashes = append(hashes, hash)
   713  	}
   714  
   715  	Convey("each node should be able to get statements form the other nodes including self", t, func() {
   716  		for i := 0; i < nodesCount; i++ {
   717  			h1 := nodes[i]
   718  			for j := 0; j < nodesCount; j++ {
   719  				h2 := nodes[j]
   720  				options := GetLinksOptions{Load: true, StatusMask: StatusLive}
   721  				fn := &APIFnGetLinks{action: *NewGetLinksAction(
   722  					&LinkQuery{
   723  						Base:       HashFromPeerID(h1.nodeID),
   724  						T:          "statement",
   725  						StatusMask: options.StatusMask,
   726  					}, &options)}
   727  				response, err := fn.Call(h2)
   728  				So(err, ShouldBeNil)
   729  				So(fmt.Sprintf("%v", response), ShouldEqual, fmt.Sprintf("&{[{%v this statement by node %d (%v) review  %s}]}", hashes[i], i, h1.nodeID, h1.nodeID.Pretty()))
   730  			}
   731  		}
   732  	})
   733  }
   734  
   735  func TestDHTMakeReciept(t *testing.T) {
   736  	d, _, h := PrepareTestChain("test")
   737  	defer CleanupTestChain(h, d)
   738  
   739  	hash, _ := NewHash("QmY8Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqh2")
   740  
   741  	Convey("it should make a receipt and signature", t, func() {
   742  		msg := h.node.NewMessage(PUT_REQUEST, HoldReq{EntryHash: hash})
   743  
   744  		data, err := MakeReceiptData(msg, ReceiptOK)
   745  		So(err, ShouldBeNil)
   746  		sig, err := h.Sign(data)
   747  		if err != nil {
   748  			panic(err)
   749  		}
   750  
   751  		receiptSig, err := h.dht.MakeReceiptSignature(msg, ReceiptOK)
   752  		So(err, ShouldBeNil)
   753  		So(receiptSig.Equal(sig), ShouldBeTrue)
   754  
   755  		matches, err := h.VerifySignature(receiptSig, string(data), h.agent.PubKey())
   756  		So(err, ShouldBeNil)
   757  		So(matches, ShouldBeTrue)
   758  
   759  		holdResp, err := h.dht.MakeHoldResp(msg, StatusRejected)
   760  		So(err, ShouldBeNil)
   761  		So(holdResp.Code, ShouldEqual, ReceiptRejected)
   762  		matches, err = h.VerifySignature(holdResp.Signature, string(data), h.agent.PubKey())
   763  		So(err, ShouldBeNil)
   764  		So(matches, ShouldBeFalse)
   765  
   766  		holdResp, err = h.dht.MakeHoldResp(msg, StatusLive)
   767  		So(err, ShouldBeNil)
   768  		So(holdResp.Code, ShouldEqual, ReceiptOK)
   769  		matches, err = h.VerifySignature(holdResp.Signature, string(data), h.agent.PubKey())
   770  		So(err, ShouldBeNil)
   771  		So(matches, ShouldBeTrue)
   772  	})
   773  }
   774  
   775  func processChangeRequestsInTesting(h *Holochain) {
   776  	for len(h.dht.changeQueue) > 0 {
   777  		req := <-h.dht.changeQueue
   778  		err := handleChangeRequests(h.dht, req)
   779  		if err != nil {
   780  			panic(err)
   781  		}
   782  	}
   783  }