github.com/decred/dcrlnd@v0.7.6/watchtower/wtserver/state_update.go (about) 1 package wtserver 2 3 import ( 4 "fmt" 5 6 "github.com/decred/dcrlnd/watchtower/wtdb" 7 "github.com/decred/dcrlnd/watchtower/wtwire" 8 ) 9 10 // handleStateUpdates processes a stream of StateUpdate requests from the 11 // client. The provided update should be the first such update read, subsequent 12 // updates will be consumed if the peer does not signal IsComplete on a 13 // particular update. 14 func (s *Server) handleStateUpdates(peer Peer, id *wtdb.SessionID, 15 update *wtwire.StateUpdate) error { 16 17 // Set the current update to the first update read off the wire. 18 // Additional updates will be read if this value is set to nil after 19 // processing the first. 20 var curUpdate = update 21 for { 22 // If this is not the first update, read the next state update 23 // from the peer. 24 if curUpdate == nil { 25 nextMsg, err := s.readMessage(peer) 26 if err != nil { 27 return err 28 } 29 30 var ok bool 31 curUpdate, ok = nextMsg.(*wtwire.StateUpdate) 32 if !ok { 33 return fmt.Errorf("client sent %T after "+ 34 "StateUpdate", nextMsg) 35 } 36 } 37 38 // Try to accept the state update from the client. 39 err := s.handleStateUpdate(peer, id, curUpdate) 40 if err != nil { 41 return err 42 } 43 44 // If the client signals that this is last StateUpdate 45 // message, we can disconnect the client. 46 if curUpdate.IsComplete == 1 { 47 return nil 48 } 49 50 // Reset the current update to read subsequent updates in the 51 // stream. 52 curUpdate = nil 53 54 select { 55 case <-s.quit: 56 return ErrServerExiting 57 default: 58 } 59 } 60 } 61 62 // handleStateUpdate processes a StateUpdate message request from a client. An 63 // attempt will be made to insert the update into the db, where it is validated 64 // against the client's session. The possible errors are then mapped back to 65 // StateUpdateCodes specified by the watchtower wire protocol, and sent back 66 // using a StateUpdateReply message. 67 func (s *Server) handleStateUpdate(peer Peer, id *wtdb.SessionID, 68 update *wtwire.StateUpdate) error { 69 70 var ( 71 lastApplied uint16 72 failCode wtwire.ErrorCode 73 err error 74 ) 75 76 sessionUpdate := wtdb.SessionStateUpdate{ 77 ID: *id, 78 Hint: update.Hint, 79 SeqNum: update.SeqNum, 80 LastApplied: update.LastApplied, 81 EncryptedBlob: update.EncryptedBlob, 82 } 83 84 lastApplied, err = s.cfg.DB.InsertStateUpdate(&sessionUpdate) 85 switch { 86 case err == nil: 87 log.Debugf("State update %d accepted for %s", 88 update.SeqNum, id) 89 90 failCode = wtwire.CodeOK 91 92 // Return a permanent failure if a client tries to send an update for 93 // which we have no session. 94 case err == wtdb.ErrSessionNotFound: 95 failCode = wtwire.CodePermanentFailure 96 97 case err == wtdb.ErrSeqNumAlreadyApplied: 98 failCode = wtwire.CodePermanentFailure 99 100 // TODO(conner): remove session state for protocol 101 // violation. Could also double as clean up method for 102 // session-related state. 103 104 case err == wtdb.ErrLastAppliedReversion: 105 failCode = wtwire.StateUpdateCodeClientBehind 106 107 case err == wtdb.ErrSessionConsumed: 108 failCode = wtwire.StateUpdateCodeMaxUpdatesExceeded 109 110 case err == wtdb.ErrUpdateOutOfOrder: 111 failCode = wtwire.StateUpdateCodeSeqNumOutOfOrder 112 113 default: 114 failCode = wtwire.CodeTemporaryFailure 115 } 116 117 if s.cfg.NoAckUpdates { 118 return &connFailure{ 119 ID: *id, 120 Code: failCode, 121 } 122 } 123 124 return s.replyStateUpdate( 125 peer, id, failCode, lastApplied, 126 ) 127 } 128 129 // replyStateUpdate sends a response to a StateUpdate from a client. If the 130 // status code in the reply is OK, the error from the write will be bubbled up. 131 // Otherwise, this method returns a connection error to ensure we don't continue 132 // communication with the client. 133 func (s *Server) replyStateUpdate(peer Peer, id *wtdb.SessionID, 134 code wtwire.StateUpdateCode, lastApplied uint16) error { 135 136 msg := &wtwire.StateUpdateReply{ 137 Code: code, 138 LastApplied: lastApplied, 139 } 140 141 err := s.sendMessage(peer, msg) 142 if err != nil { 143 log.Errorf("unable to send StateUpdateReply to %s", id) 144 } 145 146 // Return the write error if the request succeeded. 147 if code == wtwire.CodeOK { 148 return err 149 } 150 151 // Otherwise the request failed, return a connection failure to 152 // disconnect the client. 153 return &connFailure{ 154 ID: *id, 155 Code: code, 156 } 157 }