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 }