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

     1  // Copyright (C) 2013-2018, The MetaCurrency Project (Eric Harris-Braun, Arthur Brock, et. al.)
     2  // Use of this source code is governed by GPLv3 found in the LICENSE file
     3  //----------------------------------------------------------------------------------------
     4  
     5  // gossip implements the gossip protocol for the distributed hash table
     6  
     7  package holochain
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	. "github.com/holochain/holochain-proto/hash"
    13  	peer "github.com/libp2p/go-libp2p-peer"
    14  	"github.com/tidwall/buntdb"
    15  	"math/rand"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  )
    21  
    22  // Put holds a put or link for gossiping
    23  type Put struct {
    24  	Idx int
    25  	M   Message
    26  }
    27  
    28  // Gossip holds a gossip message
    29  type Gossip struct {
    30  	Puts []Put
    31  }
    32  
    33  // GossipReq holds a gossip request
    34  type GossipReq struct {
    35  	MyIdx   int
    36  	YourIdx int
    37  }
    38  
    39  // we also gossip about peers too, keeping lists of different peers e.g. blockedlist etc
    40  type PeerListType string
    41  
    42  const (
    43  	BlockedList = "blockedlist"
    44  )
    45  
    46  type PeerRecord struct {
    47  	ID      peer.ID
    48  	Warrant string // evidence, reasons, documentation of why peer is in this list
    49  }
    50  
    51  type PeerList struct {
    52  	Type    PeerListType
    53  	Records []PeerRecord
    54  }
    55  
    56  var ErrDHTErrNoGossipersAvailable error = errors.New("no gossipers available")
    57  var ErrDHTExpectedGossipReqInBody error = errors.New("expected gossip request")
    58  var ErrNoSuchIdx error = errors.New("no such change index")
    59  
    60  //HaveFingerprint returns true if we have seen the given fingerprint
    61  func (dht *DHT) HaveFingerprint(f Hash) (result bool, err error) {
    62  	index, err := dht.GetFingerprint(f)
    63  	if err == nil {
    64  		result = index >= 0
    65  	}
    66  	return
    67  }
    68  
    69  // GetFingerprint returns the index that of the message that made a change or -1 if we don't have it
    70  func (dht *DHT) GetFingerprint(f Hash) (index int, err error) {
    71  	index = -1
    72  	db := dht.ht.(*BuntHT).db
    73  	err = db.View(func(tx *buntdb.Tx) error {
    74  		idxStr, e := tx.Get("f:" + f.String())
    75  		if e == buntdb.ErrNotFound {
    76  			return nil
    77  		}
    78  		if e != nil {
    79  			return e
    80  		}
    81  		index, e = strconv.Atoi(idxStr)
    82  		if e != nil {
    83  			return e
    84  		}
    85  		return nil
    86  	})
    87  	return
    88  }
    89  
    90  // GetPuts returns a list of puts after the given index
    91  func (dht *DHT) GetPuts(since int) (puts []Put, err error) {
    92  	puts = make([]Put, 0)
    93  	db := dht.ht.(*BuntHT).db
    94  	err = db.View(func(tx *buntdb.Tx) error {
    95  		err = tx.AscendGreaterOrEqual("idx", string(since), func(key, value string) bool {
    96  			x := strings.Split(key, ":")
    97  			idx, _ := strconv.Atoi(x[1])
    98  			if idx >= since {
    99  				p := Put{Idx: idx}
   100  				if value != "" {
   101  					err := ByteDecoder([]byte(value), &p.M)
   102  					if err != nil {
   103  						return false
   104  					}
   105  				}
   106  				puts = append(puts, p)
   107  			}
   108  			return true
   109  		})
   110  		sort.Slice(puts, func(i, j int) bool { return puts[i].Idx < puts[j].Idx })
   111  		return err
   112  	})
   113  	return
   114  }
   115  
   116  // GetGossiper loads returns last known index of the gossiper, and adds them if not didn't exist before
   117  func (dht *DHT) GetGossiper(id peer.ID) (idx int, err error) {
   118  	key := "peer:" + peer.IDB58Encode(id)
   119  	db := dht.ht.(*BuntHT).db
   120  	err = db.View(func(tx *buntdb.Tx) error {
   121  		var e error
   122  		idx, e = getIntVal(key, tx)
   123  		if e != nil {
   124  			return e
   125  		}
   126  		return nil
   127  	})
   128  	return
   129  }
   130  
   131  type GossiperData struct {
   132  	ID     peer.ID
   133  	PutIdx int
   134  }
   135  
   136  func (dht *DHT) GetGossipers() (gossipers []GossiperData, err error) {
   137  	var glist []peer.ID
   138  	glist, err = dht._getGossipers()
   139  	if err != nil {
   140  		return
   141  	}
   142  	for _, id := range glist {
   143  		var idx int
   144  		idx, err = dht.GetGossiper(id)
   145  		if err != nil {
   146  			return
   147  		}
   148  		gossipers = append(gossipers, GossiperData{ID: id, PutIdx: idx})
   149  	}
   150  	return
   151  }
   152  
   153  func (dht *DHT) getGossipers() (glist []peer.ID, err error) {
   154  	glist, err = dht._getGossipers()
   155  	if err != nil {
   156  		return
   157  	}
   158  	ns := dht.config.RedundancyFactor
   159  	glist = dht.h.node.filterInactviePeers(glist, ns)
   160  	return
   161  }
   162  
   163  func (dht *DHT) _getGossipers() (glist []peer.ID, err error) {
   164  	glist = make([]peer.ID, 0)
   165  	db := dht.ht.(*BuntHT).db
   166  	err = db.View(func(tx *buntdb.Tx) error {
   167  		err = tx.Ascend("peer", func(key, value string) bool {
   168  			x := strings.Split(key, ":")
   169  			id, e := peer.IDB58Decode(x[1])
   170  			if e != nil {
   171  				return false
   172  			}
   173  			//			idx, _ := strconv.Atoi(value)
   174  			glist = append(glist, id)
   175  			return true
   176  		})
   177  		return nil
   178  	})
   179  	ns := dht.config.RedundancyFactor
   180  	if ns > 1 {
   181  		size := len(glist)
   182  		hlist := make([]Hash, size)
   183  		for i := 0; i < size; i++ {
   184  			h := HashFromPeerID(glist[i])
   185  			hlist[i] = h
   186  		}
   187  		me := HashFromPeerID(dht.h.nodeID)
   188  
   189  		hlist = SortByDistance(me, hlist)
   190  		glist = make([]peer.ID, len(hlist))
   191  		for i := 0; i < len(hlist); i++ {
   192  			glist[i] = PeerIDFromHash(hlist[i])
   193  		}
   194  	}
   195  	return
   196  }
   197  
   198  // FindGossiper picks a random DHT node to gossip with
   199  func (dht *DHT) FindGossiper() (g peer.ID, err error) {
   200  	var glist []peer.ID
   201  	glist, err = dht.getGossipers()
   202  	if err != nil {
   203  		return
   204  	}
   205  	if len(glist) == 0 {
   206  		err = ErrDHTErrNoGossipersAvailable
   207  	} else {
   208  		g = glist[rand.Intn(len(glist))]
   209  	}
   210  	return
   211  }
   212  
   213  // AddGossiper adds a new gossiper to the gossiper store
   214  func (dht *DHT) AddGossiper(id peer.ID) (err error) {
   215  	// never add ourselves as a gossiper
   216  	if id == dht.h.node.HashAddr {
   217  		return
   218  	}
   219  	err = dht.updateGossiper(id, 0)
   220  	return
   221  }
   222  
   223  // internal update gossiper function, assumes all checks have been made
   224  func (dht *DHT) updateGossiper(id peer.ID, newIdx int) (err error) {
   225  	db := dht.ht.(*BuntHT).db
   226  	err = db.Update(func(tx *buntdb.Tx) error {
   227  		key := "peer:" + peer.IDB58Encode(id)
   228  		idx, e := getIntVal(key, tx)
   229  		if e != nil {
   230  			return e
   231  		}
   232  		if newIdx < idx {
   233  			return nil
   234  		}
   235  		sidx := fmt.Sprintf("%d", newIdx)
   236  		_, _, err = tx.Set(key, sidx, nil)
   237  		if err != nil {
   238  			return err
   239  		}
   240  		return nil
   241  	})
   242  	return
   243  }
   244  
   245  // UpdateGossiper updates a gossiper
   246  func (dht *DHT) UpdateGossiper(id peer.ID, newIdx int) (err error) {
   247  	if dht.h.node.IsBlocked(id) {
   248  		dht.glog.Logf("gossiper %v on blocklist, deleting", id)
   249  		dht.DeleteGossiper(id) // ignore error
   250  		return
   251  	}
   252  	dht.glog.Logf("updating %v to %d", id, newIdx)
   253  	err = dht.updateGossiper(id, newIdx)
   254  	return
   255  }
   256  
   257  // DeleteGossiper removes a gossiper from the database
   258  func (dht *DHT) DeleteGossiper(id peer.ID) (err error) {
   259  	dht.glog.Logf("deleting %v", id)
   260  	db := dht.ht.(*BuntHT).db
   261  	err = db.Update(func(tx *buntdb.Tx) error {
   262  		key := "peer:" + peer.IDB58Encode(id)
   263  		_, e := tx.Delete(key)
   264  		return e
   265  	})
   266  	return
   267  }
   268  
   269  const (
   270  	GossipBackPutDelay = 100 * time.Millisecond
   271  )
   272  
   273  // GossipReceiver implements the handler for the gossip protocol
   274  func GossipReceiver(h *Holochain, m *Message) (response interface{}, err error) {
   275  	dht := h.dht
   276  	switch m.Type {
   277  	case GOSSIP_REQUEST:
   278  		dht.glog.Logf("GossipReceiver got: %v", m)
   279  		switch t := m.Body.(type) {
   280  		case GossipReq:
   281  			dht.glog.Logf("%v wants my puts since %d and is at %d", m.From, t.YourIdx, t.MyIdx)
   282  
   283  			// give the gossiper what they want
   284  			var puts []Put
   285  			puts, err = h.dht.GetPuts(t.YourIdx)
   286  			g := Gossip{Puts: puts}
   287  			response = g
   288  
   289  			// check to see what we know they said, and if our record is less
   290  			// that where they are currently at, gossip back
   291  			idx, e := h.dht.GetGossiper(m.From)
   292  			if e == nil && idx < t.MyIdx {
   293  				dht.glog.Logf("we only have %d of %d from %v so gossiping back", idx, t.MyIdx, m.From)
   294  
   295  				pi := h.node.host.Peerstore().PeerInfo(m.From)
   296  				if len(pi.Addrs) == 0 {
   297  					dht.glog.Logf("NO ADDRESSES FOR PEER:%v", pi)
   298  				}
   299  
   300  				// queue up a request to gossip back
   301  				go func() {
   302  					defer func() {
   303  						if r := recover(); r != nil {
   304  							// ignore writes past close
   305  						}
   306  					}()
   307  					// but give them a chance to finish handling the response
   308  					// from this request first so sleep a bit per put
   309  					time.Sleep(GossipBackPutDelay * time.Duration(len(puts)))
   310  					dht.gchan <- gossipWithReq{m.From}
   311  				}()
   312  			}
   313  
   314  		default:
   315  			err = ErrDHTExpectedGossipReqInBody
   316  		}
   317  	default:
   318  		err = fmt.Errorf("message type %d not in holochain-gossip protocol", int(m.Type))
   319  	}
   320  	return
   321  }
   322  
   323  // gossipWith gossips with a peer asking for everything after since
   324  func (dht *DHT) gossipWith(id peer.ID) (err error) {
   325  	// prevent rentrance
   326  	dht.glk.Lock()
   327  	defer dht.glk.Unlock()
   328  
   329  	dht.glog.Logf("starting gossipWith %v", id)
   330  	defer func() {
   331  		dht.glog.Logf("finish gossipWith %v, err=%v", id, err)
   332  	}()
   333  
   334  	var myIdx, yourIdx int
   335  	myIdx, err = dht.GetIdx()
   336  	if err != nil {
   337  		return
   338  	}
   339  
   340  	yourIdx, err = dht.GetGossiper(id)
   341  	if err != nil {
   342  		return
   343  	}
   344  
   345  	var r interface{}
   346  	msg := dht.h.node.NewMessage(GOSSIP_REQUEST, GossipReq{MyIdx: myIdx, YourIdx: yourIdx + 1})
   347  	r, err = dht.h.Send(dht.h.node.ctx, GossipProtocol, id, msg, 0)
   348  	if err != nil {
   349  		return
   350  	}
   351  
   352  	gossip := r.(Gossip)
   353  	puts := gossip.Puts
   354  
   355  	// gossiper has more stuff that we new about before so update the gossipers status
   356  	// and also run their puts
   357  	count := len(puts)
   358  	if count > 0 {
   359  		dht.glog.Logf("queuing %d puts:\n%v", count, puts)
   360  		var idx int
   361  		for i, p := range puts {
   362  			idx = i + yourIdx + 1
   363  			// put the message into the gossip put handling queue so we can return quickly
   364  			dht.gossipPuts <- p
   365  		}
   366  		err = dht.UpdateGossiper(id, idx)
   367  	} else {
   368  		dht.glog.Log("no new puts received")
   369  	}
   370  	return
   371  }
   372  
   373  // gossipPut handles a given put
   374  func (dht *DHT) gossipPut(p Put) (err error) {
   375  	f, e := p.M.Fingerprint()
   376  	if e == nil {
   377  		// dht.sources[p.M.From] = true
   378  		// dht.fingerprints[f.String()[2:4]] = true
   379  		dht.glog.Logf("PUT--%d (fingerprint: %v)", p.Idx, f)
   380  		exists, e := dht.HaveFingerprint(f)
   381  		if !exists && e == nil {
   382  			dht.glog.Logf("PUT--%d calling ActionReceiver", p.Idx)
   383  			r, e := ActionReceiver(dht.h, &p.M)
   384  			dht.glog.Logf("PUT--%d ActionReceiver returned %v with err %v", p.Idx, r, e)
   385  			if e != nil {
   386  				// put receiver error so do what? probably nothing because
   387  				// put will get retried
   388  			}
   389  		} else {
   390  			if e == nil {
   391  				dht.glog.Logf("already have fingerprint %v", f)
   392  			} else {
   393  				dht.glog.Logf("error in HaveFingerprint %v", e)
   394  			}
   395  		}
   396  
   397  	} else {
   398  		dht.glog.Logf("error calculating fingerprint for %v", p)
   399  	}
   400  	return
   401  }
   402  
   403  func handleGossipPut(dht *DHT, x interface{}) (err error) {
   404  	p := x.(Put)
   405  	err = dht.gossipPut(p)
   406  	return
   407  }
   408  
   409  // gossip picks a random node in my neighborhood and sends gossips with it
   410  func (dht *DHT) gossip() (err error) {
   411  
   412  	var g peer.ID
   413  	g, err = dht.FindGossiper()
   414  	if err != nil {
   415  		return
   416  	}
   417  	dht.gchan <- gossipWithReq{g}
   418  	return
   419  }
   420  
   421  // GossipTask runs a gossip and logs any errors
   422  func GossipTask(h *Holochain) {
   423  	if h.dht != nil && h.dht.gchan != nil {
   424  		err := h.dht.gossip()
   425  		if err != nil {
   426  			h.dht.glog.Logf("error: %v", err)
   427  		}
   428  	}
   429  
   430  }
   431  
   432  func handleGossipWith(dht *DHT, x interface{}) (err error) {
   433  	g := x.(gossipWithReq)
   434  	err = dht.gossipWith(g.id)
   435  	return
   436  }
   437  
   438  func (dht *DHT) handleTillDone(errtext string, channel Channel, handlerFn func(*DHT, interface{}) error) (err error) {
   439  	var done bool
   440  	for !done {
   441  		dht.glog.Logf("%s: waiting for request", errtext)
   442  		x, ok := <-channel
   443  		if !ok {
   444  			done = true
   445  			break
   446  		} else {
   447  			err = handlerFn(dht, x)
   448  			if err != nil {
   449  				dht.glog.Logf("%s: got err: %v", errtext, err)
   450  			}
   451  		}
   452  	}
   453  	dht.glog.Logf("%s: channel closed, stopping", errtext)
   454  	return nil
   455  }
   456  
   457  // HandleGossipWiths waits on a channel for gossipWith requests
   458  func (dht *DHT) HandleGossipWiths() (err error) {
   459  	err = dht.handleTillDone("HandleGossipWiths", dht.gchan, handleGossipWith)
   460  	return
   461  }
   462  
   463  // HandleGossipPuts waits on a channel for gossip changes
   464  func (dht *DHT) HandleGossipPuts() (err error) {
   465  	err = dht.handleTillDone("HandleGossipPuts", dht.gossipPuts, handleGossipPut)
   466  	return nil
   467  }
   468  
   469  // getList returns the peer list of the given type
   470  func (dht *DHT) getList(listType PeerListType) (result PeerList, err error) {
   471  	result.Type = listType
   472  	result.Records = make([]PeerRecord, 0)
   473  	db := dht.ht.(*BuntHT).db
   474  	err = db.View(func(tx *buntdb.Tx) error {
   475  		err = tx.Ascend("list", func(key, value string) bool {
   476  			x := strings.Split(key, ":")
   477  
   478  			if x[1] == string(listType) {
   479  				pid, e := peer.IDB58Decode(x[2])
   480  				if e != nil {
   481  					return false
   482  				}
   483  				r := PeerRecord{ID: pid, Warrant: value}
   484  				result.Records = append(result.Records, r)
   485  			}
   486  			return true
   487  		})
   488  		return nil
   489  	})
   490  	return
   491  }
   492  
   493  // addToList adds the peers to a list
   494  func (dht *DHT) addToList(m *Message, list PeerList) (err error) {
   495  	dht.dlog.Logf("addToList %s=>%v", list.Type, list.Records)
   496  	db := dht.ht.(*BuntHT).db
   497  	err = db.Update(func(tx *buntdb.Tx) error {
   498  		_, err = incIdx(tx, m)
   499  		if err != nil {
   500  			return err
   501  		}
   502  		for _, r := range list.Records {
   503  			k := peer.IDB58Encode(r.ID)
   504  			_, _, err = tx.Set("list:"+string(list.Type)+":"+k, r.Warrant, nil)
   505  			if err != nil {
   506  				return err
   507  			}
   508  		}
   509  		return err
   510  	})
   511  	return
   512  }