decred.org/dcrdex@v1.0.5/tatanka/tatanka_messages.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package tatanka
     5  
     6  import (
     7  	"time"
     8  
     9  	"decred.org/dcrdex/dex"
    10  	"decred.org/dcrdex/dex/msgjson"
    11  	"decred.org/dcrdex/tatanka/mj"
    12  	"decred.org/dcrdex/tatanka/tanka"
    13  )
    14  
    15  // handleInboundTatankaConnect handles an inbound tatanka connection.
    16  func (t *Tatanka) handleInboundTatankaConnect(cl tanka.Sender, msg *msgjson.Message) *msgjson.Error {
    17  	var cfg mj.TatankaConfig
    18  	if err := msg.Unmarshal(&cfg); err != nil {
    19  		return msgjson.NewError(mj.ErrBadRequest, "unmarshal error: %v", err)
    20  	}
    21  
    22  	if _, found := t.whitelist[cfg.ID]; !found {
    23  		return msgjson.NewError(mj.ErrAuth, "not whitelisted")
    24  	}
    25  
    26  	p, rrs, err := t.loadPeer(cfg.ID)
    27  	if err != nil {
    28  		return msgjson.NewError(mj.ErrInternal, "error finding peer: %v", err)
    29  	}
    30  
    31  	if err := mj.CheckSig(msg, p.PubKey); err != nil {
    32  		return msgjson.NewError(mj.ErrAuth, "bad sig")
    33  	}
    34  
    35  	// if calcTier(rep, p.BondTier()) <= 0 {
    36  	// 	return msgjson.NewError(mj.ErrAuth, "denying inbound banned tatanka node %q", p.ID)
    37  	// }
    38  
    39  	cfgMsg := mj.MustNotification(mj.RouteTatankaConfig, t.generateConfig(p.BondTier()))
    40  	if err := t.send(cl, cfgMsg); err != nil {
    41  		peerID := cl.PeerID()
    42  		t.log.Errorf("error sending configuration to connecting tatanka %q", dex.Bytes(peerID[:]))
    43  		cl.Disconnect()
    44  		return nil // don't bother
    45  	}
    46  
    47  	// TODO: Resolve our own bond tier with the tatanka node.
    48  	// myTier := t.bondTier.Load()
    49  	// if conn.BondTier != myTier {
    50  	// 	bonds, err := t.db.GetBonds(t.id)
    51  	// 	if err != nil {
    52  	// 		t.log.Errorf("Error getting bonds from DB: %v", err)
    53  	// 		return msgjson.NewError(mj.ErrInternal, "internal error")
    54  	// 	}
    55  	// 	var bondTier uint64
    56  	// 	for _, b := range bonds {
    57  	// 		bondTier += b.Strength
    58  	// 	}
    59  	// 	if conn.BondTier != myTier {
    60  	// 		bondsUpdate := mj.MustNotification(mj.RouteBonds, bonds)
    61  	// 		if err := cl.Send(bondsUpdate); err != nil {
    62  	// 			t.log.Errorf("Error sending bonds update to %s during connect: %w", p.ID, err)
    63  	// 			cl.Disconnect()
    64  	// 			return nil
    65  	// 		}
    66  	// 	}
    67  
    68  	// }
    69  
    70  	cl.SetPeerID(cfg.ID)
    71  
    72  	t.tatankasMtx.Lock()
    73  	// TODO: Track peer reconnections and ban peer if on a runaway.
    74  	if _, exists := t.tatankas[p.ID]; exists {
    75  		t.log.Debugf("Connecting Tatanka node %s replaces already connected node", cl.PeerID())
    76  	}
    77  
    78  	pp := &peer{Peer: p, Sender: cl, rrs: rrs}
    79  
    80  	// TODO: Check Tatanka Node reputation too
    81  	// if pp.banned() {
    82  	// 	t.tatankasMtx.Unlock()
    83  	// 	return msgjson.NewError(mj.ErrAuth, "inbound peer %q is banned", p.ID)
    84  	// }
    85  
    86  	tt := &remoteTatanka{peer: pp}
    87  	tt.cfg.Store(&cfg)
    88  	t.tatankas[cfg.ID] = tt
    89  	t.tatankasMtx.Unlock()
    90  
    91  	t.sendResult(cl, msg.ID, true)
    92  
    93  	return nil
    94  }
    95  
    96  // handleTatankaMessage handles all messages from remote tatanka nodes except
    97  // for mj.RouteTatankaConnect. The node is expected to already be connected.
    98  // handleTatankaMessage perfoms some initial  handling of the message, like
    99  // fetching the *remoteTatanka and checking signatures, before finding calling
   100  // the appropriate handler.
   101  func (t *Tatanka) handleTatankaMessage(cl tanka.Sender, msg *msgjson.Message) *msgjson.Error {
   102  	if t.log.Level() == dex.LevelTrace {
   103  		t.log.Tracef("Tatanka node handling message from remote tatanka. route = %s, payload = %s", msg.Route, mj.Truncate(msg.Payload))
   104  	}
   105  
   106  	peerID := cl.PeerID()
   107  	tt := t.tatankaNode(peerID)
   108  	if tt == nil {
   109  		t.log.Errorf("%q (%d) message received from unknown outbound tatanka peer %q", msg.Route, msg.ID, peerID)
   110  		return msgjson.NewError(mj.ErrAuth, "who even is this?")
   111  	}
   112  
   113  	if err := mj.CheckSig(msg, tt.PubKey); err != nil {
   114  		t.log.Errorf("Signature error for %q message from %q: %v", msg.Route, tt.ID, err)
   115  		return msgjson.NewError(mj.ErrSig, "signature doesn't check")
   116  	}
   117  
   118  	// The first message received after mj.RouteTatankaConnect must be the
   119  	// config.
   120  	if msg.Route != mj.RouteTatankaConfig && tt.cfg.Load() == nil {
   121  		t.log.Errorf("message received from tatanka peer %q before configuration", peerID)
   122  		tt.Disconnect()
   123  		return msgjson.NewError(mj.ErrNoConfig, "send your configuration first")
   124  	}
   125  
   126  	switch msg.Type {
   127  	case msgjson.Request:
   128  		switch msg.Route {
   129  		case mj.RouteRelayTankagram:
   130  			return t.handleRelayedTankagram(tt, msg)
   131  		case mj.RoutePathInquiry:
   132  			return t.handlePathInquiry(tt, msg)
   133  		default:
   134  			return msgjson.NewError(mj.ErrBadRequest, "unknown request route %q", msg.Route)
   135  		}
   136  	case msgjson.Notification:
   137  		switch msg.Route {
   138  		case mj.RouteNewClient:
   139  			t.handleNewRemoteClientNotification(tt.ID, msg)
   140  		case mj.RouteClientDisconnect:
   141  			t.handleRemoteClientDisconnect(tt.ID, msg)
   142  		case mj.RouteTatankaConfig:
   143  			t.handleTatankaConfig(tt, msg)
   144  		case mj.RouteRelayBroadcast:
   145  			t.handleRelayBroadcast(tt, msg)
   146  		default:
   147  			// TODO: What? Can't let this happen too much.
   148  			return msgjson.NewError(mj.ErrBadRequest, "unknown notification route %q", msg.Route)
   149  		}
   150  	default:
   151  		return msgjson.NewError(mj.ErrBadRequest, "unknown message type %d", msg.Type)
   152  	}
   153  	return nil
   154  }
   155  
   156  // handleRelayedTankagram handles a tankagram relayed from another node. The
   157  // mesh is currently only capable of single-hop relays.
   158  func (t *Tatanka) handleRelayedTankagram(tt *remoteTatanka, msg *msgjson.Message) *msgjson.Error {
   159  	var gram *mj.Tankagram
   160  	if err := msg.Unmarshal(&gram); err != nil {
   161  		t.log.Errorf("Error unmarshaling tankagram from %s: %w", tt.ID, err)
   162  		return msgjson.NewError(mj.ErrBadRequest, "unmarshal error")
   163  	}
   164  
   165  	t.clientMtx.RLock()
   166  	c, found := t.clients[gram.To]
   167  	t.clientMtx.RUnlock()
   168  	if !found {
   169  		t.sendResult(tt, msg.ID, &mj.TankagramResult{Result: mj.TRTNoPath})
   170  		t.log.Warnf("Tankagram relay from %s to unknown client %s", tt.ID, gram.To)
   171  		return nil
   172  	}
   173  
   174  	relayedMsg := mj.MustRequest(mj.RouteTankagram, gram)
   175  	var resB dex.Bytes
   176  	sent, clientErr, err := t.requestAnyOne([]tanka.Sender{c}, relayedMsg, &resB)
   177  	if sent {
   178  		t.sendResult(tt, msg.ID, &mj.TankagramResult{Result: mj.TRTTransmitted, Response: resB})
   179  		return nil
   180  	}
   181  	if clientErr != nil {
   182  		t.sendResult(tt, msg.ID, &mj.TankagramResult{Result: mj.TRTErrBadClient})
   183  		return nil
   184  	}
   185  	if err != nil {
   186  		t.log.Errorf("Error sending to local client %s: %v", gram.To, err)
   187  		t.sendResult(tt, msg.ID, &mj.TankagramResult{Result: mj.TRTErrFromTanka})
   188  		return nil
   189  	}
   190  	// We might get here if the context expires during the call to requestOne.
   191  	return nil
   192  }
   193  
   194  // handlePathInquire returns whether a particular client is connected to this
   195  // node.
   196  func (t *Tatanka) handlePathInquiry(tt *remoteTatanka, msg *msgjson.Message) *msgjson.Error {
   197  	var inq mj.PathInquiry
   198  	if err := msg.Unmarshal(&inq); err != nil {
   199  		t.log.Errorf("Failed to unmarshal path inquiry from %s: %v", tt.ID, err)
   200  	}
   201  
   202  	t.clientMtx.RLock()
   203  	_, found := t.clients[inq.ID]
   204  	t.clientMtx.RUnlock()
   205  	t.sendResult(tt, msg.ID, found)
   206  	return nil
   207  }
   208  
   209  // handleNewRemoteClientNotification handles an mj.RouteNewClient notification,
   210  // updating the t.remoteClients map.
   211  func (t *Tatanka) handleNewRemoteClientNotification(ttID tanka.PeerID, msg *msgjson.Message) {
   212  	if t.skipRelay(msg) {
   213  		return
   214  	}
   215  
   216  	var conn mj.Connect
   217  	if err := msg.Unmarshal(&conn); err != nil {
   218  		t.log.Errorf("error unmarshaling %s notification payload: %q", msg.Route, err)
   219  		return
   220  	}
   221  	t.registerRemoteClient(ttID, conn.ID)
   222  }
   223  
   224  // handleRemoteClientDisconnect handles an mj.RouteClientDisconnect
   225  // notification, updating the t.remoteClients map.
   226  func (t *Tatanka) handleRemoteClientDisconnect(ttID tanka.PeerID, msg *msgjson.Message) {
   227  	if t.skipRelay(msg) {
   228  		return
   229  	}
   230  
   231  	var dconn mj.Disconnect
   232  	if err := msg.Unmarshal(&dconn); err != nil {
   233  		t.log.Errorf("error unmarshaling %s notification payload: %q", msg.Route, err)
   234  		return
   235  	}
   236  
   237  	job := &clientJob{
   238  		task: &clientJobRemoteDisconnect{
   239  			clientID: dconn.ID,
   240  			tankaID:  ttID,
   241  		},
   242  		res: make(chan interface{}, 1),
   243  	}
   244  	t.clientJobs <- job
   245  	<-job.res
   246  }
   247  
   248  // handleTatankaConfig handles the mj.RouteTatankaConfig notification,
   249  // storing a remote tatanka node's updated configuration info.
   250  func (t *Tatanka) handleTatankaConfig(tt *remoteTatanka, msg *msgjson.Message) {
   251  	peerID := tt.PeerID()
   252  
   253  	var cfg mj.TatankaConfig
   254  	if err := msg.Unmarshal(&cfg); err != nil {
   255  		tt.Disconnect()
   256  		t.log.Errorf("failed to parse tatanka config from %q: %w", peerID, err)
   257  		return
   258  	}
   259  
   260  	tt.cfg.Store(&cfg)
   261  }
   262  
   263  // handleRelayBroadcast distributes the broadcast to any locally connected
   264  // subscribers.
   265  func (t *Tatanka) handleRelayBroadcast(tt *remoteTatanka, msg *msgjson.Message) {
   266  	if t.skipRelay(msg) {
   267  		return
   268  	}
   269  
   270  	var bcast *mj.Broadcast
   271  	if err := msg.Unmarshal(&bcast); err != nil || bcast == nil || bcast.Topic == "" {
   272  		t.log.Errorf("error unmarshaling broadcast from %s: %w", tt.ID, err)
   273  		return
   274  	}
   275  
   276  	if time.Since(bcast.Stamp) > tanka.EpochLength || time.Until(bcast.Stamp) > tanka.EpochLength {
   277  		t.log.Errorf("Ignoring relayed broadcast with old stamp received from %s", tt.ID)
   278  		return
   279  	}
   280  
   281  	t.registerRemoteClient(tt.ID, tt.ID)
   282  
   283  	if msgErr := t.distributeBroadcastedMessage(bcast, false); msgErr != nil {
   284  		t.log.Errorf("error distributing broadcast: %v", msgErr)
   285  		return
   286  	}
   287  }