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