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

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