Exemple #1
0
 def calculate_hash(self, base_block_hash: bytes) -> bytes:
     """ Hash of the Bitcoin produced header, this is used for the block hash.
     """
     from hathor.merged_mining.bitcoin import build_merkle_root_from_path, sha256d_hash
     coinbase_tx_hash = sha256d_hash(self.coinbase_head + base_block_hash + self.coinbase_tail)
     merkle_root = bytes(reversed(build_merkle_root_from_path([coinbase_tx_hash] + self.merkle_path)))
     return sha256d_hash(self.header_head + merkle_root + self.header_tail)
Exemple #2
0
 def build_aux_pow(self, work: SingleMinerWork) -> BitcoinAuxPow:
     """ Build the Auxiliary Proof-of-Work from job and work data.
     """
     from hathor.merged_mining.bitcoin import sha256d_hash
     bitcoin_header, coinbase_tx = self._make_bitcoin_block_and_coinbase(work)
     header = bytes(bitcoin_header)
     header_head, header_tail = header[:36], header[-12:]
     block_base_hash = sha256d_hash(self.hathor_data)
     coinbase = bytes(coinbase_tx)
     coinbase_head, coinbase_tail = coinbase.split(block_base_hash)
     return BitcoinAuxPow(header_head, coinbase_head, coinbase_tail, self.merkle_path, header_tail)
Exemple #3
0
    def handle_submit(self, params: List[Any], msgid: Optional[str]) -> None:
        """ Handles submit request by validating and propagating the result

        - params: rpc_user, job_id, xnonce2, time, nonce

        Example:

        - ['', '6a16cffa-47c0-41d9-b92f-44e05d3c25dd', '0000000000000000', 'c359f65c', '47c8f488']
        """
        from hathor.merged_mining.bitcoin import sha256d_hash

        self.log.debug('handle submit', msgid=msgid, params=params)

        work = SingleMinerWork.from_stratum_params(self.xnonce1, params)

        job = self.jobs.get(work.job_id)
        if not job:
            self.log.error('job not found', job_id=work.job_id)
            return

        bitcoin_block_header = job.build_bitcoin_block_header(work)
        block_base = job.hathor_data
        block_base_hash = sha256d_hash(block_base)
        self.log.debug('work received', bitcoin_header=bytes(bitcoin_block_header).hex(),
                       block_base=block_base.hex(), block_base_hash=block_base_hash.hex(),
                       hash=bitcoin_block_header.hash.hex())

        aux_pow = job.build_aux_pow(work)
        aux_pow.verify(block_base_hash)  # TODO: treat exception (respond with proper error)

        self.log.debug('forward work to hathor', aux_pow=aux_pow)
        self.submit_to_hathor(job, aux_pow)

        # XXX: work is always sent to bitcoin node, which rejects accordingly
        self.log.debug('forward work to bitcoin', work=work)
        self.submit_to_bitcoin(job, work)

        # TODO: don't always return success?
        self.send_result(True, msgid)
Exemple #4
0
    def new_single_miner_job(self, payback_address_bitcoin: str, xnonce_size: int) -> SingleMinerJob:
        """ Generate a partial job for a single miner, based on this job.
        """
        from hathor.merged_mining.bitcoin import sha256d_hash

        # base txs for merkle tree, before coinbase
        transactions = self.bitcoin_coord.transactions

        # build coinbase transaction with hathor block hash
        hathor_block_hash = sha256d_hash(self.hathor_coord.data)
        coinbase_tx = self.bitcoin_coord.make_coinbase_transaction(
            hathor_block_hash,
            self.payback_address_bitcoin,
            xnonce_size,
        )
        coinbase_bytes = bytes(coinbase_tx)
        coinbase_head, coinbase_tail = coinbase_bytes.split(hathor_block_hash + b'\0' * xnonce_size, 1)
        coinbase_head += hathor_block_hash
        assert len(coinbase_bytes) == len(coinbase_head) + xnonce_size + len(coinbase_tail)  # just a sanity check

        # TODO: check if total transaction size increase exceed size and sigop limits, there's probably an RPC for this
        transactions.insert(0, coinbase_tx)

        return SingleMinerJob(
            job_id=str(uuid4()),
            prev_hash=self.bitcoin_coord.previous_block_hash,
            coinbase_head=coinbase_head,
            coinbase_tail=coinbase_tail,
            merkle_path=build_merkle_path_for_coinbase([tx.hash for tx in transactions]),
            version=self.bitcoin_coord.version,
            bits=self.bitcoin_coord.bits,
            hathor_data=self.hathor_coord.data,
            hathor_job_id=self.hathor_coord.job_id,
            timestamp=self.bitcoin_coord.get_timestamp(),
            transactions=transactions,
            clean=self.clean,
        )
Exemple #5
0
    def handle_submit(self, params: Dict, msgid: Optional[str]) -> None:
        """ Handles submit request by validating and propagating the result

        :param params: a dict containing a valid uui4 hex as `job_id` and a valid transaction nonce as `nonce`
        :type params: Dict

        :param msgid: JSON-RPC 2.0 message id
        :type msgid: Optional[UUID]
        """
        from hathor.merged_mining.bitcoin import sha256d_hash

        self.log.debug('handle submit', msgid=msgid, params=params)

        if 'job_id' not in params or 'nonce' not in params:
            return self.send_error(INVALID_PARAMS, msgid, {
                'params': params,
                'required': ['job_id', 'nonce']
            })

        if not valid_uuid(params['job_id']):
            return self.send_error(INVALID_PARAMS, msgid, {
                'job_id': params['job_id'],
                'message': 'job_id is invalid uuid4'
            })

        job_id = UUID(params['job_id'])
        job = self.jobs.get(job_id)

        if job is None:
            return self.send_error(
                JOB_NOT_FOUND, msgid, {
                    'current_job': self.current_job
                    and self.current_job.id.hex,
                    'job_id': job_id.hex
                })

        # It may take a while for pubsub to get a new job.
        # To avoid propagating the same tx multiple times, we check if it has already been submitted.
        if job is not self.current_job or job.submitted is not None:
            return self.send_error(
                STALE_JOB, msgid, {
                    'current_job': self.current_job
                    and self.current_job.id.hex,
                    'job_id': job_id.hex
                })

        tx = job.tx.clone()
        block_base = tx.get_header_without_nonce()
        block_base_hash = sha256d_hash(block_base)
        # Stratum sends the nonce as a big-endian hexadecimal string.
        if params.get('aux_pow'):
            assert isinstance(
                tx, MergeMinedBlock
            ), 'expected MergeMinedBlock got ' + type(tx).__name__
            tx.aux_pow = BitcoinAuxPow.from_bytes(
                bytes.fromhex(params['aux_pow']))
            tx.nonce = 0
        else:
            tx.nonce = int(params['nonce'], 16)
        tx.update_hash()
        assert tx.hash is not None

        self.log.debug('share received',
                       block=tx,
                       block_base=block_base.hex(),
                       block_base_hash=block_base_hash.hex())

        try:
            tx.verify_pow(job.weight)
        except PowError:
            self.log.error('bad share, discard', job_weight=job.weight, tx=tx)
            return self.send_error(
                INVALID_SOLUTION, msgid, {
                    'hash': tx.hash.hex(),
                    'target': int(tx.get_target()).to_bytes(32, 'big').hex()
                })

        job.submitted = self.factory.get_current_timestamp()
        self.completed_jobs += 1

        # answer the miner soon, so it can start a new job
        self.send_result('ok', msgid)
        self.manager.reactor.callLater(0, self.job_request)

        try:
            tx.verify_pow()
        except PowError:
            # Transaction pow was not enough, but the share was succesfully submited
            self.log.info('high hash, keep mining', tx=tx)
            return
        else:
            self.log.info('low hash, new block candidate', tx=tx)

        if isinstance(tx, Block):
            try:
                # We only propagate blocks here in stratum
                # For tx we need to propagate in the resource,
                # so we can get the possible errors
                self.manager.submit_block(tx, fails_silently=False)
                self.blocks_found += 1
            except (InvalidNewTransaction, TxValidationError) as e:
                # Block propagation failed, but the share was succesfully submited
                self.log.warn('block propagation failed', block=tx, error=e)
            else:
                self.log.info('new block found', block=tx)
        elif isinstance(tx, Transaction):
            self.log.info('transaction mined', tx=tx)
            funds_hash = tx.get_funds_hash()
            if funds_hash in self.factory.mining_tx_pool:
                self.factory.mined_txs[funds_hash] = tx
                del self.factory.mining_tx_pool[funds_hash]
                if funds_hash in self.factory.tx_queue:
                    self.factory.tx_queue.remove(funds_hash)
                if funds_hash in self.factory.deferreds_tx:
                    # Return to resolve the resource to send back the response
                    d = self.factory.deferreds_tx.pop(funds_hash)
                    d.callback(tx)
        else:
            assert False, 'tx should either be a Block or Transaction'
Exemple #6
0
 def get_base_hash(self) -> bytes:
     from hathor.merged_mining.bitcoin import sha256d_hash
     return sha256d_hash(self.get_header_without_nonce())