docs: def parse_transaction(self, block_id, tx):
          """
          Given a block ID and an data-bearing transaction, 
          try to parse it into a virtual chain operation.
          
          Use the implementation's 'db_parse' method to do so.
  
          Data transactions that do not have the magic bytes or a valid opcode
          will be skipped automatically.  The db_parse method does not need
          to know how to handle them.
  
          @tx is a dict with
          `txid`: the transaction ID
          `txindex`: the offset in the block where this tx occurs
          `nulldata`: the hex-encoded scratch data from the transaction
          `ins`: the list of transaction inputs
          `outs`: the list of transaction outputs
          `senders`: the list of transaction senders
          `fee`: the transaction fee
          `txhex`: the hex-encoded raw transaction
          
          Return a dict representing the data on success.
          Return None on error
          """
          
          data_hex = tx['nulldata']
          inputs = tx['ins']
          outputs = tx['outs']
          senders = tx['senders']
          fee = tx['fee']
          txhex = tx['hex']
          merkle_path = tx['tx_merkle_path']
          
          if not is_hex(data_hex):
              # should always work; the tx downloader converts the binary string to hex
              # not a valid hex string 
              raise ValueError("Invalid nulldata: not hex-encoded")
          
          if len(data_hex) % 2 != 0:
              # should always work; the tx downloader converts the binary string to hex
              # not valid hex string 
              raise ValueError("Invalid nulldata: not hex-encoded")
          
          data_bin = None
          try:
              # should always work; the tx downloader converts the binary string to hex
              data_bin = data_hex.decode('hex')
          except Exception, e:
              log.error("Failed to parse transaction: %s (data_hex = %s)" % (tx, data_hex))
              raise ValueError("Invalid nulldata: not hex-encoded")
          
          if not data_bin.startswith(self.magic_bytes):
              # not for us
              return None
          
          if len(data_bin) < len(self.magic_bytes) + 1:
              # invalid operation--no opcode
              return None
  
          # 3rd byte is always the operation code
          op_code = data_bin[len(self.magic_bytes)]
          if op_code not in self.opcodes:
              return None 
          
          # looks like an op.  Try to parse it.
          op_payload = data_bin[len(self.magic_bytes)+1:]
          
          op = self.impl.db_parse(block_id, tx['txid'], tx['txindex'], op_code, op_payload, senders, inputs, outputs, fee, db_state=self.state, raw_tx=txhex)
          if op is None:
              # not valid 
              return None 
          
          # store it
          op['virtualchain_opcode'] = op_code
          op['virtualchain_txid'] = tx['txid']
          op['virtualchain_txindex'] = tx['txindex']
          op['virtualchain_txhex'] = txhex
          op['virtualchain_tx_merkle_path'] = merkle_path
          op['virtualchain_senders'] = senders
          op['virtualchain_fee'] = fee
          op['virtualchain_data_hex'] = op_payload.encode('hex')
          
          return op