docs: def serialize_op( cls, opcode, opdata, opfields, verbose=True ):
          """
          Given an opcode (byte), associated data (dict), and the operation
          fields to serialize (opfields), convert it 
          into its canonical serialized form (i.e. in order to 
          generate a consensus hash.
  
          opdata is allowed to have extra fields.  They will be ignored
  
          Return the canonical form on success.
          Return None on error.
          """
          fields = opfields.get( opcode, None )
          if fields is None:
              log.error("BUG: unrecongnized opcode '%s'" % opcode )
              return None 
  
          all_values = []
          debug_all_values = []
          missing = []
          for field in fields:
             if not opdata.has_key(field):
                missing.append( field )
  
             field_value = opdata.get(field, None)
             if field_value is None:
                field_value = ""
            
             # netstring format
             debug_all_values.append( str(field) + "=" + str(len(str(field_value))) + ":" + str(field_value) )
             all_values.append( str(len(str(field_value))) + ":" + str(field_value) )
  
          if len(missing) > 0:
             log.error("Missing fields; dump follows:\n{}".format(simplejson.dumps( opdata, indent=4, sort_keys=True )))
             raise Exception("BUG: missing fields '{}'".format(",".join(missing)))
  
          if verbose:
              log.debug("SERIALIZE: {}:{}".format(opcode, ",".join(debug_all_values) ))
  
          field_values = ",".join( all_values )
  
          return opcode + ":" + field_values