Example #1
0
    async def submit_work(self, header_hash: bytes, nonce: int,
                          mixhash: bytes) -> bool:
        if not self.remote:
            raise ValueError("Should only be used for remote miner")

        if header_hash not in self.work_map:
            return False
        # this copy is necessary since there might be multiple submissions concurrently
        block = copy.deepcopy(self.work_map[header_hash])
        header = block.header
        header.nonce, header.mixhash = nonce, mixhash

        # sign as a guardian
        if self.guardian_private_key and isinstance(block, RootBlock):
            header.sign_with_private_key(self.guardian_private_key)

        try:
            await self.add_block_async_func(block)
            # a previous submission of the same work could have removed the key
            if header_hash in self.work_map:
                del self.work_map[header_hash]
                self.current_work = None
            return True
        except Exception:
            Logger.error_exception()
            return False
Example #2
0
 async def _run(self) -> None:
     """
     overrides BasePeer._run()
     forwards decrypted messages to QuarkChain Peer
     """
     self.run_child_service(self.boot_manager)
     self.secure_peer.add_sync_task()
     if self.secure_peer.state == ConnectionState.CONNECTING:
         self.secure_peer.state = ConnectionState.ACTIVE
         self.secure_peer.active_future.set_result(None)
     try:
         while self.is_operational:
             metadata, raw_data = await self.secure_peer.read_metadata_and_raw_data(
             )
             self.run_task(
                 self.secure_peer.secure_handle_metadata_and_raw_data(
                     metadata, raw_data))
     except (PeerConnectionLost, TimeoutError) as err:
         self.logger.debug("%s stopped responding (%r), disconnecting",
                           self.remote, err)
     except DecryptionError as err:
         self.logger.warning(
             "Unable to decrypt message from %s, disconnecting: %r",
             self.remote, err)
     except Exception as e:
         self.logger.error("Unknown exception from %s, message: %r",
                           self.remote, e)
         Logger.error_exception()
     self.secure_peer.abort_in_flight_rpcs()
     self.secure_peer.close()
Example #3
0
    async def handle_sync_minor_block_list_request(self, req):
        """ Raises on error"""
        async def __download_blocks(block_hash_list):
            op, resp, rpc_id = await peer_shard_conn.write_rpc_request(
                CommandOp.GET_MINOR_BLOCK_LIST_REQUEST,
                GetMinorBlockListRequest(block_hash_list),
            )
            return resp.minor_block_list

        shard = self.shards.get(req.branch, None)
        if not shard:
            return SyncMinorBlockListResponse(error_code=errno.EBADMSG)
        peer_shard_conn = shard.peers.get(req.cluster_peer_id, None)
        if not peer_shard_conn:
            return SyncMinorBlockListResponse(error_code=errno.EBADMSG)

        BLOCK_BATCH_SIZE = 100
        block_hash_list = req.minor_block_hash_list
        # empty
        if not block_hash_list:
            return SyncMinorBlockListResponse(error_code=0)

        try:
            while len(block_hash_list) > 0:
                blocks_to_download = block_hash_list[:BLOCK_BATCH_SIZE]
                try:
                    block_chain = await asyncio.wait_for(
                        __download_blocks(blocks_to_download), TIMEOUT)
                except asyncio.TimeoutError as e:
                    Logger.info(
                        "[{}] sync request from master failed due to timeout".
                        format(req.branch.get_full_shard_id()))
                    raise e

                Logger.info(
                    "[{}] sync request from master, downloaded {} blocks ({} - {})"
                    .format(
                        req.branch.get_full_shard_id(),
                        len(block_chain),
                        block_chain[0].header.height,
                        block_chain[-1].header.height,
                    ))
                check(len(block_chain) == len(blocks_to_download))

                add_block_success = await self.slave_server.add_block_list_for_sync(
                    block_chain)
                if not add_block_success:
                    raise RuntimeError(
                        "Failed to add minor blocks for syncing root block")
                block_hash_list = block_hash_list[BLOCK_BATCH_SIZE:]

            branch = block_chain[0].header.branch
            shard = self.slave_server.shards.get(branch, None)
            check(shard is not None)
            return SyncMinorBlockListResponse(
                error_code=0, shard_stats=shard.state.get_shard_stats())
        except Exception:
            Logger.error_exception()
            return SyncMinorBlockListResponse(error_code=1)
Example #4
0
    async def add_block(self, block):
        """ Returns true if block is successfully added. False on any error.
        called by 1. local miner (will not run if syncing) 2. SyncTask
        """
        old_tip = self.state.header_tip
        try:
            xshard_list = self.state.add_block(block)
        except Exception as e:
            Logger.error_exception()
            return False

        # only remove from pool if the block successfully added to state,
        #   this may cache failed blocks but prevents them being broadcasted more than needed
        # TODO add ttl to blocks in new_block_pool
        self.state.new_block_pool.pop(block.header.get_hash(), None)
        # block has been added to local state, broadcast tip so that peers can sync if needed
        try:
            if old_tip != self.state.header_tip:
                self.broadcast_new_tip()
        except Exception:
            Logger.warning_every_sec("broadcast tip failure", 1)

        # block already existed in local shard state
        # but might not have been propagated to other shards and master
        # let's make sure all the shards and master got it before return
        if xshard_list is None:
            future = self.add_block_futures.get(block.header.get_hash(), None)
            if future:
                Logger.info(
                    "[{}] {} is being added ... waiting for it to finish".
                    format(block.header.branch.get_shard_id(),
                           block.header.height))
                await future
            return True

        self.add_block_futures[
            block.header.get_hash()] = self.loop.create_future()

        # Start mining new one before propagating inside cluster
        # The propagation should be done by the time the new block is mined
        self.miner.mine_new_block_async()
        prev_root_height = self.state.db.get_root_block_by_hash(
            block.header.hash_prev_root_block).header.height
        await self.slave.broadcast_xshard_tx_list(block, xshard_list,
                                                  prev_root_height)
        await self.slave.send_minor_block_header_to_master(
            block.header,
            len(block.tx_list),
            len(xshard_list),
            self.state.get_shard_stats(),
        )

        self.add_block_futures[block.header.get_hash()].set_result(None)
        del self.add_block_futures[block.header.get_hash()]
        return True
Example #5
0
    async def add_block_list_for_sync(self, block_list):
        """ Add blocks in batch to reduce RPCs. Will NOT broadcast to peers.

        Returns true if blocks are successfully added. False on any error.
        Additionally, returns list of coinbase_amount_map for each block
        This function only adds blocks to local and propagate xshard list to other shards.
        It does NOT notify master because the master should already have the minor header list,
        and will add them once this function returns successfully.
        """
        coinbase_amount_list = []
        if not block_list:
            return True, coinbase_amount_list

        existing_add_block_futures = []
        block_hash_to_x_shard_list = dict()
        for block in block_list:
            check(
                block.header.branch.get_full_shard_id() == self.full_shard_id)

            block_hash = block.header.get_hash()
            try:
                xshard_list, coinbase_amount_map = self.state.add_block(
                    block, skip_if_too_old=False)
                # coinbase_amount_map may be None if the block exists
                # adding the block header one since the block is already validated.
                coinbase_amount_list.append(block.header.coinbase_amount_map)
            except Exception as e:
                Logger.error_exception()
                return False, coinbase_amount_list

            # block already existed in local shard state
            # but might not have been propagated to other shards and master
            # let's make sure all the shards and master got it before return
            if xshard_list is None:
                future = self.add_block_futures.get(block_hash, None)
                if future:
                    existing_add_block_futures.append(future)
            else:
                prev_root_height = self.state.db.get_root_block_header_by_hash(
                    block.header.hash_prev_root_block).height
                block_hash_to_x_shard_list[block_hash] = (xshard_list,
                                                          prev_root_height)
                self.add_block_futures[block_hash] = self.loop.create_future()

        await self.slave.batch_broadcast_xshard_tx_list(
            block_hash_to_x_shard_list, block_list[0].header.branch)

        for block_hash in block_hash_to_x_shard_list.keys():
            self.add_block_futures[block_hash].set_result(None)
            del self.add_block_futures[block_hash]

        await asyncio.gather(*existing_add_block_futures)

        return True, coinbase_amount_list
Example #6
0
    async def handle_sync_minor_block_list_request(self, req):
        async def __download_blocks(block_hash_list):
            op, resp, rpc_id = await peer_shard_conn.write_rpc_request(
                CommandOp.GET_MINOR_BLOCK_LIST_REQUEST,
                GetMinorBlockListRequest(block_hash_list),
            )
            return resp.minor_block_list

        shard = self.shards.get(req.branch, None)
        if not shard:
            return SyncMinorBlockListResponse(error_code=errno.EBADMSG)
        peer_shard_conn = shard.peers.get(req.cluster_peer_id, None)
        if not peer_shard_conn:
            return SyncMinorBlockListResponse(error_code=errno.EBADMSG)

        BLOCK_BATCH_SIZE = 100
        try:
            block_hash_list = req.minor_block_hash_list
            while len(block_hash_list) > 0:
                blocks_to_download = block_hash_list[:BLOCK_BATCH_SIZE]
                block_chain = await __download_blocks(blocks_to_download)
                Logger.info(
                    "[{}] sync request from master, downloaded {} blocks ({} - {})".format(
                        req.branch.get_shard_id(),
                        len(block_chain),
                        block_chain[0].header.height,
                        block_chain[-1].header.height,
                    )
                )
                check(len(block_chain) == len(blocks_to_download))

                add_block_success = await self.slave_server.add_block_list_for_sync(
                    block_chain
                )
                if not add_block_success:
                    raise RuntimeError(
                        "Failed to add minor blocks for syncing root block"
                    )
                block_hash_list = block_hash_list[BLOCK_BATCH_SIZE:]
        except Exception:
            Logger.error_exception()
            return SyncMinorBlockListResponse(error_code=1)

        return SyncMinorBlockListResponse(error_code=0)
Example #7
0
    async def submit_work(
        self,
        header_hash: bytes,
        nonce: int,
        mixhash: bytes,
        signature: Optional[bytes] = None,
    ) -> bool:
        if not self.remote:
            raise ValueError("Should only be used for remote miner")

        if header_hash not in self.work_map:
            return False
        # this copy is necessary since there might be multiple submissions concurrently
        block = copy.deepcopy(self.work_map[header_hash])
        header = block.header

        # reject if tip updated
        tip_hash = self.get_header_tip_func().get_hash()
        if header.hash_prev_block != tip_hash:
            del self.work_map[header_hash]
            return False

        header.nonce, header.mixhash = nonce, mixhash
        # sign using the root_signer_private_key
        if self.root_signer_private_key and isinstance(block, RootBlock):
            header.sign_with_private_key(self.root_signer_private_key)

        # remote sign as a guardian
        if isinstance(block, RootBlock) and signature is not None:
            header.signature = signature

        try:
            await self.add_block_async_func(block)
            # a previous submission of the same work could have removed the key
            if header_hash in self.work_map:
                del self.work_map[header_hash]
            return True
        except Exception:
            Logger.error_exception()
            return False
Example #8
0
 async def handle_mined_block():
     while True:
         res = await self.output_q.coro_get()  # type: MiningResult
         if not res:
             return  # empty result means ending
         # start mining before processing and propagating mined block
         self._mine_new_block_async()
         block = self.work_map[res.header_hash]
         block.header.nonce = res.nonce
         block.header.mixhash = res.mixhash
         del self.work_map[res.header_hash]
         self._track(block)
         try:
             # FIXME: Root block should include latest minor block headers while it's being mined
             # This is a hack to get the latest minor block included since testnet does not check difficulty
             if self.consensus_type == ConsensusType.POW_SIMULATE:
                 block = await self.create_block_async_func()
                 block.header.nonce = random.randint(0, 2**32 - 1)
                 self._track(block)
                 self._log_status(block)
             await self.add_block_async_func(block)
         except Exception:
             Logger.error_exception()
Example #9
0
    async def submit_work(self, header_hash: bytes, nonce: int,
                          mixhash: bytes) -> bool:
        if not self.remote:
            raise ValueError("Should only be used for remote miner")

        if header_hash not in self.work_map:
            return False
        # this copy is necessary since there might be multiple submissions concurrently
        block = copy.deepcopy(self.work_map[header_hash])
        header = block.header
        header.nonce, header.mixhash = nonce, mixhash

        # lower the difficulty for root block signed by guardian
        if self.guardian_private_key and isinstance(block, RootBlock):
            diff = Guardian.adjust_difficulty(header.difficulty, header.height)
            try:
                validate_seal(header, self.consensus_type, adjusted_diff=diff)
            except ValueError:
                return False
            # sign as a guardian
            header.sign_with_private_key(self.guardian_private_key)
        else:  # minor block, or doesn't have guardian private key
            try:
                validate_seal(header, self.consensus_type)
            except ValueError:
                return False
        try:
            await self.add_block_async_func(block)
            # a previous submission of the same work could have removed the key
            if header_hash in self.work_map:
                del self.work_map[header_hash]
                self.current_work = None
            return True
        except Exception:
            Logger.error_exception()
            return False
Example #10
0
    async def add_block_list_for_sync(self, block_list):
        """ Add blocks in batch to reduce RPCs. Will NOT broadcast to peers.

        Returns true if blocks are successfully added. False on any error.
        Additionally, returns list of coinbase_amount_map for each block
        This function only adds blocks to local and propagate xshard list to other shards.
        It does NOT notify master because the master should already have the minor header list,
        and will add them once this function returns successfully.
        """
        coinbase_amount_list = []
        if not block_list:
            return True, coinbase_amount_list

        existing_add_block_futures = []
        block_hash_to_x_shard_list = dict()
        uncommitted_block_header_list = []
        uncommitted_coinbase_amount_map_list = []
        for block in block_list:
            check(
                block.header.branch.get_full_shard_id() == self.full_shard_id)

            block_hash = block.header.get_hash()
            # adding the block header one assuming the block will be validated.
            coinbase_amount_list.append(block.header.coinbase_amount_map)

            commit_status, future = self.__get_block_commit_status_by_hash(
                block_hash)
            if commit_status == BLOCK_COMMITTED:
                # Skip processing the block if it is already committed
                Logger.warning(
                    "minor block to sync {} is already committed".format(
                        block_hash.hex()))
                continue
            elif commit_status == BLOCK_COMMITTING:
                # Check if the block is being propagating to other slaves and the master
                # Let's make sure all the shards and master got it before committing it
                Logger.info(
                    "[{}] {} is being added ... waiting for it to finish".
                    format(block.header.branch.to_str(), block.header.height))
                existing_add_block_futures.append(future)
                continue

            check(commit_status == BLOCK_UNCOMMITTED)
            # Validate and add the block
            try:
                xshard_list, coinbase_amount_map = self.state.add_block(
                    block, skip_if_too_old=False, force=True)
            except Exception as e:
                Logger.error_exception()
                return False, None

            prev_root_height = self.state.db.get_root_block_header_by_hash(
                block.header.hash_prev_root_block).height
            block_hash_to_x_shard_list[block_hash] = (xshard_list,
                                                      prev_root_height)
            self.add_block_futures[block_hash] = self.loop.create_future()
            uncommitted_block_header_list.append(block.header)
            uncommitted_coinbase_amount_map_list.append(
                block.header.coinbase_amount_map)

        await self.slave.batch_broadcast_xshard_tx_list(
            block_hash_to_x_shard_list, block_list[0].header.branch)
        check(
            len(uncommitted_coinbase_amount_map_list) == len(
                uncommitted_block_header_list))
        await self.slave.send_minor_block_header_list_to_master(
            uncommitted_block_header_list,
            uncommitted_coinbase_amount_map_list)

        # Commit all blocks and notify all rest add block operations
        for block_header in uncommitted_block_header_list:
            block_hash = block_header.get_hash()
            self.state.commit_by_hash(block_hash)
            Logger.debug("committed mblock {}".format(block_hash.hex()))

            self.add_block_futures[block_hash].set_result(None)
            del self.add_block_futures[block_hash]

        # Wait for the other add block operations
        await asyncio.gather(*existing_add_block_futures)

        return True, coinbase_amount_list
Example #11
0
    async def add_block(self, block):
        """ Returns true if block is successfully added. False on any error.
        called by 1. local miner (will not run if syncing) 2. SyncTask
        """

        block_hash = block.header.get_hash()
        commit_status, future = self.__get_block_commit_status_by_hash(
            block_hash)
        if commit_status == BLOCK_COMMITTED:
            return True
        elif commit_status == BLOCK_COMMITTING:
            Logger.info(
                "[{}] {} is being added ... waiting for it to finish".format(
                    block.header.branch.to_str(), block.header.height))
            await future
            return True

        check(commit_status == BLOCK_UNCOMMITTED)
        # Validate and add the block
        old_tip = self.state.header_tip
        try:
            xshard_list, coinbase_amount_map = self.state.add_block(block,
                                                                    force=True)
        except Exception as e:
            Logger.error_exception()
            return False

        # only remove from pool if the block successfully added to state,
        # this may cache failed blocks but prevents them being broadcasted more than needed
        # TODO add ttl to blocks in new_block_header_pool
        self.state.new_block_header_pool.pop(block_hash, None)
        # block has been added to local state, broadcast tip so that peers can sync if needed
        try:
            if old_tip != self.state.header_tip:
                self.broadcast_new_tip()
        except Exception:
            Logger.warning_every_sec("broadcast tip failure", 1)

        # Add the block in future and wait
        self.add_block_futures[block_hash] = self.loop.create_future()

        prev_root_height = self.state.db.get_root_block_header_by_hash(
            block.header.hash_prev_root_block).height
        await self.slave.broadcast_xshard_tx_list(block, xshard_list,
                                                  prev_root_height)
        await self.slave.send_minor_block_header_to_master(
            block.header,
            len(block.tx_list),
            len(xshard_list),
            coinbase_amount_map,
            self.state.get_shard_stats(),
        )

        # Commit the block
        self.state.commit_by_hash(block_hash)
        Logger.debug("committed mblock {}".format(block_hash.hex()))

        # Notify the rest
        self.add_block_futures[block_hash].set_result(None)
        del self.add_block_futures[block_hash]
        return True
Example #12
0
    async def handle_sync_minor_block_list_request(self, req):
        """ Raises on error"""
        async def __download_blocks(block_hash_list):
            op, resp, rpc_id = await peer_shard_conn.write_rpc_request(
                CommandOp.GET_MINOR_BLOCK_LIST_REQUEST,
                GetMinorBlockListRequest(block_hash_list),
            )
            return resp.minor_block_list

        shard = self.shards.get(req.branch, None)
        if not shard:
            return SyncMinorBlockListResponse(error_code=errno.EBADMSG)
        peer_shard_conn = shard.peers.get(req.cluster_peer_id, None)
        if not peer_shard_conn:
            return SyncMinorBlockListResponse(error_code=errno.EBADMSG)

        BLOCK_BATCH_SIZE = 100
        block_hash_list = req.minor_block_hash_list
        block_coinbase_map = {}
        # empty
        if not block_hash_list:
            return SyncMinorBlockListResponse(error_code=0)

        try:
            while len(block_hash_list) > 0:
                blocks_to_download = block_hash_list[:BLOCK_BATCH_SIZE]
                try:
                    block_chain = await asyncio.wait_for(
                        __download_blocks(blocks_to_download), TIMEOUT)
                except asyncio.TimeoutError as e:
                    Logger.info(
                        "[{}] sync request from master failed due to timeout".
                        format(req.branch.to_str()))
                    raise e

                Logger.info(
                    "[{}] sync request from master, downloaded {} blocks ({} - {})"
                    .format(
                        req.branch.to_str(),
                        len(block_chain),
                        block_chain[0].header.height,
                        block_chain[-1].header.height,
                    ))

                # Step 1: Check if the len is correct
                if len(block_chain) != len(blocks_to_download):
                    raise RuntimeError(
                        "Failed to add minor blocks for syncing root block: " +
                        "length of downloaded block list is incorrect")

                # Step 2: Check if the blocks are valid
                add_block_success, coinbase_amount_list = await self.slave_server.add_block_list_for_sync(
                    block_chain)
                if not add_block_success:
                    raise RuntimeError(
                        "Failed to add minor blocks for syncing root block")
                check(len(blocks_to_download) == len(coinbase_amount_list))
                for hash, coinbase in zip(blocks_to_download,
                                          coinbase_amount_list):
                    if coinbase:
                        block_coinbase_map[hash] = coinbase
                block_hash_list = block_hash_list[BLOCK_BATCH_SIZE:]

            branch = block_chain[0].header.branch
            shard = self.slave_server.shards.get(branch, None)
            check(shard is not None)
            return SyncMinorBlockListResponse(
                error_code=0,
                shard_stats=shard.state.get_shard_stats(),
                block_coinbase_map=block_coinbase_map,
            )
        except Exception:
            Logger.error_exception()
            return SyncMinorBlockListResponse(error_code=1)