Example #1
0
class Miner:
    def __init__(self,
                 chain_manager,
                 pre_block_logic,
                 mining_address: bytes,
                 mining_thread_count):
        self.lock = threading.RLock()

        self.qryptonight_7_miner = CNv1Miner(pre_block_logic,
                                             mining_address,
                                             mining_thread_count,
                                             self.lock)

        self.qrandomx_miner = QRandomXMiner(chain_manager,
                                            pre_block_logic,
                                            mining_address,
                                            mining_thread_count,
                                            self.lock)
        self._qn = Qryptonight()
        self._chain_manager = chain_manager
        self._pre_block_logic = pre_block_logic
        self._mining_block = None
        self._current_difficulty = None
        self._current_target = None
        self._measurement = None

        self._current_miner = self.qrandomx_miner

    def isRunning(self):
        return self._current_miner.isRunning

    def get_miner(self, height, dev_config: DevConfig):
        if height < dev_config.hard_fork_heights[0]:
            self._current_miner = self.qryptonight_7_miner
            return self.qryptonight_7_miner
        else:
            self._current_miner = self.qrandomx_miner
            return self.qrandomx_miner

    def solutionAvailable(self):
        return self._current_miner.solutionAvailable()

    def prepare_next_unmined_block_template(self,
                                            mining_address,
                                            tx_pool,
                                            parent_block: Block,
                                            parent_difficulty,
                                            dev_config: DevConfig):
        miner = self.get_miner(parent_block.block_number + 1, dev_config)
        try:
            logger.debug('Miner-Try - prepare_next_unmined_block_template')
            with self.lock:
                logger.debug('Miner-Locked - prepare_next_unmined_block_template')

                logger.debug('Miner-TryCancel - prepare_next_unmined_block_template')
                miner.cancel()
                logger.debug('Miner-Cancel - prepare_next_unmined_block_template')

                self._mining_block = self.create_block(last_block=parent_block,
                                                       mining_nonce=0,
                                                       tx_pool=tx_pool,
                                                       miner_address=mining_address)

                parent_metadata = self._chain_manager.get_block_metadata(parent_block.headerhash)
                self._measurement = self._chain_manager.get_measurement(dev_config,
                                                                        self._mining_block.timestamp,
                                                                        self._mining_block.prev_headerhash,
                                                                        parent_metadata)

                self._current_difficulty, self._current_target = DifficultyTracker.get(
                    measurement=self._measurement,
                    parent_difficulty=parent_difficulty,
                    dev_config=dev_config)

        except Exception as e:
            logger.warning("Exception in start_mining")
            logger.exception(e)

    def start_mining(self,
                     parent_block: Block,
                     parent_difficulty,
                     dev_config: DevConfig):
        logger.debug('!!! Mine #{} | {} ({}) | {} -> {} | {} '.format(
            self._mining_block.block_number,
            self._measurement, self._mining_block.timestamp - parent_block.timestamp,
            UInt256ToString(parent_difficulty), UInt256ToString(self._current_difficulty),
            bin2hstr(bytearray(self._current_target))
        ))
        logger.debug('!!! Mine #{} | blob: {}'.format(
            self._mining_block.block_number,
            bin2hstr(bytearray(self._mining_block.mining_blob(dev_config)))
        ))
        miner = self.get_miner(parent_block.block_number + 1, dev_config)
        miner.start_mining(self._mining_block, self._current_target, dev_config)

    def create_block(self,
                     last_block,
                     mining_nonce,
                     tx_pool: TransactionPool,
                     miner_address) -> Optional[Block]:
        seed_block = self._chain_manager.get_block_by_number(self._qn.get_seed_height(last_block.block_number + 1))
        dev_config = self._chain_manager.get_config_by_block_number(block_number=last_block.block_number + 1)

        dummy_block = Block.create(dev_config=dev_config,
                                   block_number=last_block.block_number + 1,
                                   prev_headerhash=last_block.headerhash,
                                   prev_timestamp=last_block.timestamp,
                                   transactions=[],
                                   miner_address=miner_address,
                                   seed_height=seed_block.block_number,
                                   seed_hash=seed_block.headerhash)
        dummy_block.set_nonces(dev_config, mining_nonce, 0)

        t_pool2 = tx_pool.transactions

        block_size = dummy_block.size
        block_size_limit = self._chain_manager.get_block_size_limit(last_block, dev_config)

        transactions = []
        state_container = self._chain_manager.new_state_container(set(),
                                                                  last_block.block_number,
                                                                  True,
                                                                  None)
        for tx_set in t_pool2:
            tx = tx_set[1].transaction

            # Skip Transactions for later, which doesn't fit into block
            if block_size + tx.size + dev_config.tx_extra_overhead > block_size_limit:
                continue

            if not self._chain_manager.update_state_container(tx, state_container):
                logger.error("[create_block] Error updating state_container")
                return None

            if not tx.validate_all(state_container, check_nonce=False):
                if not state_container.revert_update():
                    return None
                tx_pool.remove_tx_from_pool(tx)
                continue
            if not self._chain_manager.apply_txn(tx, state_container):
                logger.error("[create_block] Failed to apply txn")
                if not state_container.revert_update():
                    return None
                continue

            addr_from_pk_state = state_container.addresses_state[tx.addr_from]
            addr_from_pk = Transaction.get_slave(tx)
            if addr_from_pk:
                addr_from_pk_state = state_container.addresses_state[addr_from_pk]

            tx._data.nonce = addr_from_pk_state.nonce
            block_size += tx.size + dev_config.tx_extra_overhead
            transactions.append(tx)

        block = Block.create(dev_config=dev_config,
                             block_number=last_block.block_number + 1,
                             prev_headerhash=last_block.headerhash,
                             prev_timestamp=last_block.timestamp,
                             transactions=transactions,
                             miner_address=miner_address,
                             seed_height=seed_block.block_number,
                             seed_hash=seed_block.headerhash)

        return block

    def get_block_to_mine(self,
                          wallet_address,
                          tx_pool,
                          last_block,
                          last_block_difficulty) -> list:
        dev_config = self._chain_manager.get_config_by_block_number(last_block.block_number + 1)
        try:
            mining_address = bytes(hstr2bin(wallet_address[1:].decode()))

            if not OptimizedAddressState.address_is_valid(mining_address):
                raise ValueError("[get_block_to_mine] Invalid Wallet Address %s", wallet_address)
        except Exception as e:
            raise ValueError("Error while decoding wallet address %s", e)

        if self._mining_block:
            if last_block.headerhash == self._mining_block.prev_headerhash:
                if self._mining_block.transactions[0].coinbase.addr_to == mining_address:
                    return [bin2hstr(self._mining_block.mining_blob(dev_config)),
                            int(bin2hstr(self._current_difficulty), 16)]
                else:
                    self._mining_block.update_mining_address(dev_config, mining_address)  # Updates only Miner Address

        self.prepare_next_unmined_block_template(mining_address,
                                                 tx_pool,
                                                 last_block,
                                                 last_block_difficulty,
                                                 dev_config=dev_config)

        return [bin2hstr(self._mining_block.mining_blob(dev_config)),
                int(bin2hstr(self._current_difficulty), 16)]

    def submit_mined_block(self, blob: bytes) -> bool:
        dev_config = self._chain_manager.get_config_by_block_number(self._mining_block.block_number - 1)
        if not self._mining_block.verify_blob(blob, dev_config):
            return False

        blockheader = copy.deepcopy(self._mining_block.blockheader)
        blockheader.set_mining_nonce_from_blob(blob, dev_config)

        dev_config = self._chain_manager.get_config_by_block_number(blockheader.block_number)

        if not self._chain_manager.validate_mining_nonce(blockheader, dev_config):
            return False

        self._mining_block.set_nonces(dev_config,
                                      blockheader.mining_nonce,
                                      blockheader.extra_nonce)
        cloned_block = copy.deepcopy(self._mining_block)
        return self._pre_block_logic(cloned_block)

    def cancel(self):
        self.qryptonight_7_miner.cancel()
        self.qrandomx_miner.cancel()
Example #2
0
    def validate(self, chain_manager, future_blocks: OrderedDict) -> bool:
        if chain_manager.get_block_is_duplicate(self):
            logger.warning('Duplicate Block #%s %s', self.block_number, bin2hstr(self.headerhash))
            return False

        parent_block = chain_manager.get_block(self.prev_headerhash)
        dev_config = chain_manager.get_config_by_block_number(self.block_number)

        # If parent block not found in state, then check if its in the future block list
        if not parent_block:
            try:
                parent_block = future_blocks[self.prev_headerhash]
            except KeyError:
                logger.warning('Parent block not found')
                logger.warning('Parent block headerhash %s', bin2hstr(self.prev_headerhash))
                return False

        if not self._validate_parent_child_relation(parent_block):
            logger.warning('Failed to validate blocks parent child relation')
            return False

        if not chain_manager.validate_mining_nonce(self.blockheader, dev_config):
            logger.warning('Failed PoW Validation')
            return False

        if len(self.transactions) == 0:
            return False

        try:
            state_container = chain_manager.new_state_container(set(),
                                                                self.block_number,
                                                                False,
                                                                None)

            coinbase_txn = Transaction.from_pbdata(self.transactions[0])
            coinbase_amount = coinbase_txn.amount

            if not coinbase_txn.validate_all(state_container):
                return False

            if self.block_number != 2078158:
                for proto_tx in self.transactions[1:]:
                    if proto_tx.WhichOneof('transactionType') == 'coinbase':
                        logger.warning("Multiple coinbase transaction found")
                        return False

        except Exception as e:
            logger.warning('Exception %s', e)
            return False

        # Build transaction merkle tree, calculate fee reward, and then see if BlockHeader also agrees.
        hashedtransactions = []

        for tx in self.transactions:
            tx = Transaction.from_pbdata(tx)
            hashedtransactions.append(tx.txhash)

        fee_reward = 0
        for index in range(1, len(self.transactions)):
            fee_reward += self.transactions[index].fee

        qn = Qryptonight()
        seed_block = chain_manager.get_block_by_number(qn.get_seed_height(self.block_number))

        self.blockheader._seed_height = seed_block.block_number
        self.blockheader._seed_hash = seed_block.headerhash

        if not self.blockheader.validate(fee_reward,
                                         coinbase_amount,
                                         merkle_tx_hash(hashedtransactions),
                                         dev_config):
            return False

        return True
Example #3
0
class MiningAPIService(MiningAPIServicer):
    MAX_REQUEST_QUANTITY = 100

    def __init__(self, qrlnode: QRLNode):
        self.qrlnode = qrlnode
        self._qn = Qryptonight()

    @GrpcExceptionWrapper(qrlmining_pb2.GetBlockMiningCompatibleResp,
                          StatusCode.UNKNOWN)
    def GetBlockMiningCompatible(
            self, request: qrlmining_pb2.GetBlockMiningCompatibleReq,
            context) -> qrlmining_pb2.GetBlockMiningCompatibleResp:

        blockheader, block_metadata = self.qrlnode.get_blockheader_and_metadata(
            request.height)

        response = qrlmining_pb2.GetBlockMiningCompatibleResp()
        if blockheader is not None and block_metadata is not None:
            response = qrlmining_pb2.GetBlockMiningCompatibleResp(
                blockheader=blockheader.pbdata,
                blockmetadata=block_metadata.pbdata)

        return response

    @GrpcExceptionWrapper(qrlmining_pb2.GetLastBlockHeaderResp,
                          StatusCode.UNKNOWN)
    def GetLastBlockHeader(self, request: qrlmining_pb2.GetLastBlockHeaderReq,
                           context) -> qrlmining_pb2.GetLastBlockHeaderResp:
        response = qrlmining_pb2.GetLastBlockHeaderResp()

        blockheader, block_metadata = self.qrlnode.get_blockheader_and_metadata(
            request.height)

        response.difficulty = int(bin2hstr(block_metadata.block_difficulty),
                                  16)
        response.height = self.qrlnode.block_height
        response.timestamp = blockheader.timestamp
        response.reward = blockheader.block_reward + blockheader.fee_reward
        response.hash = bin2hstr(blockheader.headerhash)
        response.depth = self.qrlnode.block_height - blockheader.block_number

        return response

    @GrpcExceptionWrapper(qrlmining_pb2.GetBlockToMineResp, StatusCode.UNKNOWN)
    def GetBlockToMine(self, request: qrlmining_pb2.GetBlockToMineReq,
                       context) -> qrlmining_pb2.GetBlockToMineResp:

        response = qrlmining_pb2.GetBlockToMineResp()

        blocktemplate_blob_and_difficulty = self.qrlnode.get_block_to_mine(
            request.wallet_address)

        if blocktemplate_blob_and_difficulty:
            response.blocktemplate_blob = blocktemplate_blob_and_difficulty[0]
            response.difficulty = blocktemplate_blob_and_difficulty[1]
            response.height = self.qrlnode.block_height + 1
            response.reserved_offset = config.dev.extra_nonce_offset
            seed_block_number = self._qn.get_seed_height(response.height)
            response.seed_hash = bin2hstr(
                self.qrlnode.get_block_header_hash_by_number(
                    seed_block_number))

        return response

    @GrpcExceptionWrapper(qrlmining_pb2.GetBlockToMineResp, StatusCode.UNKNOWN)
    def SubmitMinedBlock(self, request: qrlmining_pb2.SubmitMinedBlockReq,
                         context) -> qrlmining_pb2.SubmitMinedBlockResp:
        response = qrlmining_pb2.SubmitMinedBlockResp()

        response.error = not self.qrlnode.submit_mined_block(request.blob)

        return response