(required by virtualchain state engine)
   
   Parse a blockstack operation from a transaction.  The transaction fields are as follows:
   * `block_id` is the blockchain height at which this transaction occurs
   * `txid` is the transaction ID
   * `data` is the scratch area of the transaction that contains the actual virtualchain operation (e.g. "id[opcode][payload]")
   * `senders` is a list in 1-to-1 correspondence with `inputs` that contains information about what funded the inputs
   * `inputs` are the list of inputs to the transaction.  Some blockchains (like Bitcoin) support multiple inputs, whereas others (like Ethereum) support only 1.
   * `outputs` are the list of outputs of the transaction.  Some blockchains (like Bitcoin) support multiple outputs, whereas others (like Ethereum) support only 1.
   * `fee` is the transaction fee.

   `db_state` is the StateEngine-derived class.  This is a BlockstackDB instance.
   `**virtualchain_hints` is a dict with extra virtualchain hints that may be relevant.  We require:
   * `raw_tx`: the hex-encoded string containing the raw transaction.

   Returns a dict with the parsed operation on success.
   Return None on error

def db_parse( block_id, txid, vtxindex, op, data, senders, inputs, outputs, fee, db_state=None, **virtualchain_hints ):
   """
   (required by virtualchain state engine)
   
   Parse a blockstack operation from a transaction.  The transaction fields are as follows:
   * `block_id` is the blockchain height at which this transaction occurs
   * `txid` is the transaction ID
   * `data` is the scratch area of the transaction that contains the actual virtualchain operation (e.g. "id[opcode][payload]")
   * `senders` is a list in 1-to-1 correspondence with `inputs` that contains information about what funded the inputs
   * `inputs` are the list of inputs to the transaction.  Some blockchains (like Bitcoin) support multiple inputs, whereas others (like Ethereum) support only 1.
   * `outputs` are the list of outputs of the transaction.  Some blockchains (like Bitcoin) support multiple outputs, whereas others (like Ethereum) support only 1.
   * `fee` is the transaction fee.

   `db_state` is the StateEngine-derived class.  This is a BlockstackDB instance.
   `**virtualchain_hints` is a dict with extra virtualchain hints that may be relevant.  We require:
   * `raw_tx`: the hex-encoded string containing the raw transaction.

   Returns a dict with the parsed operation on success.
   Return None on error
   """
   # basic sanity checks 
   if len(senders) == 0:
       raise Exception("No senders given")

   if not check_tx_sender_types(senders, block_id):
       log.warning('Invalid senders for {}'.format(txid))
       return None

   # this virtualchain instance must give the 'raw_tx' hint
   assert 'raw_tx' in virtualchain_hints, 'BUG: incompatible virtualchain: requires raw_tx support'

   # internal sanity check 
   raw_tx = virtualchain_hints['raw_tx']
   btc_tx_data = virtualchain.btc_tx_deserialize(raw_tx)
   test_btc_tx = virtualchain.btc_tx_serialize({'ins': inputs, 'outs': outputs, 'locktime': btc_tx_data['locktime'], 'version': btc_tx_data['version']})
   assert raw_tx == test_btc_tx, 'TX mismatch: {} != {}'.format(raw_tx, test_btc_tx)

   # make sure each op has all the right fields defined 
   try:
       opcode = op_get_opcode_name(op)
       assert opcode is not None, "Unrecognized opcode '%s'"  % op
   except Exception, e:
       log.exception(e)
       log.error("Skipping unrecognized opcode")
       return None

   log.debug("PARSE %s at (%s, %s): %s" % (opcode, block_id, vtxindex, data.encode('hex')))

   # get the data
   op_data = None
   try:
       op_data = op_extract( opcode, data, senders, inputs, outputs, block_id, vtxindex, txid )
   except Exception, e:
       log.exception(e)
       op_data = None

   if op_data is not None:
       try:
           assert 'op' in op_data, 'BUG: missing op'
       except Exception as e:
           log.exception(e)
           log.error("BUG: missing op")
           os.abort()

       original_op_data = copy.deepcopy(op_data)

       # propagate tx data 
       op_data['vtxindex'] = int(vtxindex)
       op_data['txid'] = str(txid)
       op_data['__original_op_data__'] = original_op_data

   else:
       log.error("Unparseable op '%s'" % opcode)

   return op_data