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  }