Ejemplo n.º 1
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:
        '''
        Extracting secret from redeem transaction based on contract address.

        Args:
            contract_address (str): address of the contract atomic swap contract

        Returns:
            str, None: Secret string or None if contract wasn't redeemed yet.

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.extract_secret_from_redeem_transaction('2N7Gxryn4dD1mdyGM3DMxMAwD7k3RBTJ1gP')
            90f6b9b9a34acb486654b3e9cdc02cce0b8e40a8845924ffda68453ac2477d20
        '''
        data = clove_req_json(
            f'{cls.blockcypher_url()}/addrs/{contract_address}/full')
        if not data:
            logger.error('Unexpected response from blockcypher')
            raise ValueError('Unexpected response from blockcypher')

        transactions = data['txs']
        if len(transactions) == 1:
            logger.debug('Contract was not redeemed yet.')
            return

        return cls.extract_secret(
            scriptsig=transactions[0]['inputs'][0]['script'])
Ejemplo n.º 2
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:

        query = """
        {
            allAddressTxes(orderBy: TIME_ASC, condition: { address: "%s" }) {
                nodes {
                    txId
                }
            }
        }
        """ % (contract_address)

        data = clove_req_json(f'{cls.api_url}/graphql',
                              post_data={'query': query})
        contract_transactions = data['data']['allAddressTxes']['nodes']

        if not contract_transactions:
            logger.error(
                f'Cannot get contract transactions ({cls.symbols[0]})')
            return
        if len(contract_transactions) < 2:
            logger.debug(
                'There is no redeem transaction on this contract yet.')
            return

        redeem_transaction = cls.get_transaction(
            contract_transactions[1]['txId'])
        if not redeem_transaction:
            logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})')
            return

        return cls.extract_secret(scriptsig=redeem_transaction['vinsByTxId']
                                  ['nodes'][0]['scriptSig'])
Ejemplo n.º 3
0
    def get_latest_block(cls) -> Optional[int]:
        '''Returns the number of the latest block.'''

        query = '''
        {
            allBlocks(orderBy: HEIGHT_DESC, first: 1) {
                nodes {
                    height
                }
            }
        }
        '''

        try:
            json_response = clove_req_json(f'{cls.api_url}/graphql',
                                           post_data={'query': query})
            latest_block = json_response['data']['allBlocks']['nodes'][0][
                'height']
        except (TypeError, KeyError):
            logger.error(
                f'Cannot get latest block, bad response ({cls.symbols[0]})')
            return
        if not latest_block:
            logger.debug(f'Latest block not found ({cls.symbols[0]})')
            return
        logger.debug(f'Latest block found: {latest_block}')
        return latest_block
Ejemplo n.º 4
0
    def get_fee(cls) -> Optional[float]:
        '''
        Getting actual fee per kb

        Returns:
            float, None: actual fee per kb or None if eg. API is not responding

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.get_fee()
            0.00024538
        '''
        try:
            # This endpoint is available from v0.3.1
            fee = clove_req_json(
                f'{cls.api_url}/utils/estimatefee?nbBlocks=1')['1']
        except (TypeError, KeyError):
            logger.error(
                f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1'
            )
            return cls._calculate_fee()

        if fee == -1:
            logger.debug(f'Incorrect value in estimatedFee: {fee}')
            return cls._calculate_fee()
        fee = float(fee)
        if fee > 0:
            logger.warning(
                f'Got fee = 0 for ({cls.symbols[0]}), calculating manually')
            return fee
        return cls._calculate_fee()
Ejemplo n.º 5
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:
        '''
        Extracting secret from redeem transaction based on contract address.

        Args:
            contract_address (str): address of the contract atomic swap contract

        Returns:
            str, None: Secret string or None if contract wasn't redeemed yet.

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.extract_secret_from_redeem_transaction('2N7Gxryn4dD1mdyGM3DMxMAwD7k3RBTJ1gP')
            90f6b9b9a34acb486654b3e9cdc02cce0b8e40a8845924ffda68453ac2477d20
        '''
        contract_transactions = clove_req_json(
            f'{cls.api_url}/addr/{contract_address}')['transactions']
        if not contract_transactions:
            logger.error(
                f'Cannot get contract transactions ({cls.symbols[0]})')
            return
        if len(contract_transactions) < 2:
            logger.debug(
                'There is no redeem transaction on this contract yet.')
            return
        redeem_transaction = cls.get_transaction(contract_transactions[0])
        if not redeem_transaction:
            logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})')
            return
        return cls.extract_secret(
            scriptsig=redeem_transaction['vin'][0]['scriptSig']['hex'])
Ejemplo n.º 6
0
    def get_latest_block(cls) -> Optional[int]:
        '''
        Returns the number of the latest block.

        Returns:
            int, None: number of the latest block or None if API is not working

        Example:
            >>> from clove.network import Bitcoin
            >>> network = Bitcoin()
            >>> network.get_latest_block()
            544989
        '''
        try:
            latest_block = clove_req_json(
                f'{cls.api_url}/status?q=getInfo')['info']['blocks']
        except (TypeError, KeyError):
            logger.error(
                f'Cannot get latest block, bad response ({cls.symbols[0]})')
            return
        if not latest_block:
            logger.debug(f'Latest block not found ({cls.symbols[0]})')
            return
        logger.debug(f'Latest block found: {latest_block}')
        return latest_block
Ejemplo n.º 7
0
 def get_balance(cls, wallet_address: str) -> Optional[float]:
     data = clove_req_json(
         f'{cls.blockcypher_url()}/addrs/{wallet_address}/balance')
     if data is None:
         logger.error('Could not get details for address %s in %s network',
                      wallet_address, cls.symbols[0])
         return
     return from_base_units(data['balance'] or data['unconfirmed_balance'])
Ejemplo n.º 8
0
    def capture_messages(self,
                         expected_message_types: list,
                         timeout: int = 20,
                         buf_size: int = 1024,
                         ignore_empty: bool = False) -> list:

        deadline = time() + timeout
        found = []
        partial_message = None

        while expected_message_types and time() < deadline:

            try:
                received_data = self.connection.recv(buf_size)
                if partial_message:
                    received_data = partial_message + received_data
            except socket.timeout:
                continue

            if not received_data:
                sleep(0.1)
                continue

            for raw_message in self.split_message(received_data):

                try:
                    message = MsgSerializable.from_bytes(raw_message)
                except (SerializationError, SerializationTruncationError,
                        ValueError):
                    partial_message = raw_message
                    continue

                partial_message = None
                if not message:
                    # unknown message type, skipping
                    continue

                msg_type = type(message)

                if msg_type is msg_ping:
                    logger.debug('Got ping, sending pong.')
                    self.send_pong(message)
                elif msg_type is msg_version:
                    logger.debug('Saving version')
                    self.protocol_version = message

                if msg_type in expected_message_types:
                    found.append(message)
                    expected_message_types.remove(msg_type)
                    logger.debug('Found %s, %s more to catch',
                                 msg_type.command.upper(),
                                 len(expected_message_types))

        if not expected_message_types:
            return found

        if not ignore_empty:
            logger.error('Not all messages could be captured')
Ejemplo n.º 9
0
 def _get_block_hash(cls, block_number: int) -> str:
     '''Getting block hash by its number'''
     try:
         block_hash = clove_req_json(f'{cls.api_url}/block-index/{block_number}')['blockHash']
     except (TypeError, KeyError):
         logger.error(f'Cannot get block hash for block {block_number} ({cls.symbols[0]})')
         return
     logger.debug(f'Found hash for block {block_number}: {block_hash}')
     return block_hash
Ejemplo n.º 10
0
 def get_fee(cls) -> Optional[float]:
     '''Returns actual fee per kb.'''
     response = clove_req_json(cls.fee_endpoint)
     fee = response.get('high_fee_per_kb')
     if not fee:
         logger.error(
             'Cannot find the right key (high_fee_per_kb) while getting fee in blockcypher.'
         )
         return
     return from_base_units(fee)
Ejemplo n.º 11
0
 def get_latest_block(cls) -> Optional[int]:
     '''Returns the number of the latest block.'''
     try:
         latest_block = clove_req_json(f'{cls.api_url}/status?q=getInfo')['info']['blocks']
     except (TypeError, KeyError):
         logger.error(f'Cannot get latest block, bad response ({cls.symbols[0]})')
         return
     if not latest_block:
         logger.debug(f'Latest block not found ({cls.symbols[0]})')
         return
     logger.debug(f'Latest block found: {latest_block}')
     return latest_block
Ejemplo n.º 12
0
 def extract_secret_from_redeem_transaction(cls, contract_address: str) -> Optional[str]:
     contract_transactions = clove_req_json(f'{cls.api_url}/txids/{contract_address}')
     if not contract_transactions:
         logger.error(f'Cannot get contract transactions ({cls.symbols[0]})')
         return
     if len(contract_transactions) < 2:
         logger.debug('There is no redeem transaction on this contract yet.')
         return
     redeem_transaction = cls.get_transaction(contract_transactions[1])
     if not redeem_transaction:
         logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})')
         return
     return cls.extract_secret(redeem_transaction['hex'])
Ejemplo n.º 13
0
 def get_fee(cls) -> float:
     """Ravencoin has a different endpoint for fee (estimatesmartfee, not estimatefee)"""
     try:
         fee = clove_req_json(f'{cls.api_url}/utils/estimatesmartfee?nbBlocks=1')['1']
     except (TypeError, KeyError):
         logger.error(
             f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1'
         )
         return cls._calculate_fee()
     if fee > 0:
         return fee
     logger.warning(f'({cls.symbols[0]}) Got fee = 0, calculating manually')
     return cls._calculate_fee()
Ejemplo n.º 14
0
def clove_req_json(url: str, post_data={}):
    """
    Make a request with Clove user-agent header and return json response

    Args:
        url (str): url to get data from

    Returns:
        dict: response data

    Raises:
        ExternalApiRequestLimitExceeded: if response status code is 429

    Example:
        >>> from clove.utils.external_source import clove_req_json
        >>> clove_req_json('https://testnet.blockexplorer.com/api/status?q=getInfo')
        >>> {'info': {
        >>>      'blocks': 1414831,
        >>>      'connections': 23,
        >>>      'difficulty': 1,
        >>>      'errors': 'Warning: unknown new rules activated (versionbit 28)',
        >>>      'network': 'testnet',
        >>>      'protocolversion': 70012,
        >>>      'proxy': '',
        >>>      'relayfee': 1e-05,
        >>>      'testnet': True,
        >>>      'timeoffset': 0,
        >>>      'version': 120100}}
    """

    logger.debug('  Requesting: %s', url)
    request_start = time.time()

    if post_data:
        resp = requests.post(url, data=post_data)
    else:
        resp = requests.get(url, headers={'User-Agent': 'Clove'})

    response_time = time.time() - request_start
    logger.debug('Got response: %s [%.2fs]', url, response_time)

    if resp.status_code == 429:
        logger.error(f'Requests limit exceeded when requesting url: {url}')
        raise ExternalApiRequestLimitExceeded(f'url: {url}')

    if resp.status_code != 200:
        logger.error(f'Unexpected status code when requesting url: {url}')
        logger.debug(resp.content)
        return

    return resp.json()
Ejemplo n.º 15
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:
        data = clove_req_json(
            f'{cls.blockcypher_url()}/addrs/{contract_address}/full')
        if not data:
            logger.error('Unexpected response from blockcypher')
            raise ValueError('Unexpected response from blockcypher')

        transactions = data['txs']
        if len(transactions) == 1:
            logger.debug('Contract was not redeemed yet.')
            return

        return cls.extract_secret(
            scriptsig=transactions[0]['inputs'][0]['script'])
Ejemplo n.º 16
0
    def get_fee(cls) -> Optional[float]:
        # This endpoint is available from v0.3.1
        try:
            fee = clove_req_json(f'{cls.api_url}/utils/estimatefee?nbBlocks=1')['1']
        except (TypeError, KeyError):
            logger.error(
                f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1'
            )
            return cls._calculate_fee()

        if fee == -1:
            logger.debug(f'Incorrect value in estimatedFee: {fee}')
            return cls._calculate_fee()
        fee = float(fee)
        if fee > 0:
            logger.warning(f'Got fee = 0 for ({cls.symbols[0]}), calculating manually')
            return fee
        return cls._calculate_fee()
Ejemplo n.º 17
0
 def atomic_swap(
     self,
     sender_address: str,
     recipient_address: str,
     value: float,
     solvable_utxo: list = None,
     secret_hash: str = None,
 ) -> BitcoinAtomicSwapTransaction:
     if not solvable_utxo:
         solvable_utxo = self.get_utxo(sender_address, value)
         if not solvable_utxo:
             logger.error(f'Cannot get UTXO for address {sender_address}')
             return
     transaction = BitcoinAtomicSwapTransaction(self, sender_address,
                                                recipient_address, value,
                                                solvable_utxo, secret_hash)
     transaction.create_unsigned_transaction()
     return transaction
Ejemplo n.º 18
0
    def get_fee(cls) -> Optional[float]:
        '''
        Getting actual fee per kb

        Returns:
            float, None: actual fee per kb or None if eg. API is not responding

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.get_fee()
            0.00024538
        '''
        response = clove_req_json(cls.blockcypher_url())
        fee = response.get('high_fee_per_kb')
        if not fee:
            logger.error(
                'Cannot find the right key (high_fee_per_kb) while getting fee in blockcypher.'
            )
            return
        return from_base_units(fee)
Ejemplo n.º 19
0
    def publish(cls, raw_transaction: str) -> str:
        '''
        Publish signed transaction via block explorer API.

        Args:
            raw_transaction (str): signed transaction

        Returns:
            str: transaction address if transaction was published

        Raises:
            ValueError: if something went wrong
        '''
        url = f'{cls.api_url}/tx/send'
        logger.debug('  Requesting: %s', url)
        request_start = time.time()
        response = requests.post(url, data={'rawtx': raw_transaction})
        response_time = time.time() - request_start
        logger.debug('Got response: %s [%.2fs]', url, response_time)
        if response.status_code == 200:
            try:
                return response.json()['txid']
            except (JSONDecodeError, TypeError, KeyError):
                logger.error(
                    f'Unexpected response from API when publishing transaction ({cls.symbols[0]})'
                )
                raise ValueError(
                    'Unexpected response format from API. Please try again.')
        if response.status_code == 400:
            logger.debug(
                f'Error while publishing transaction via API ({cls.symbols[0]}): {response.text}'
            )
            raise ValueError(
                f'Unexepected error: ({response.text}). Please try again or check your transaction.'
            )
        logger.error(
            f'Unexepected error while publishing transaction via API ({cls.symbols[0]}). '
            'Status code: {response.status.code}')
        raise ValueError('Unexpected response from API. Please try again.')
Ejemplo n.º 20
0
    def atomic_swap(
        self,
        sender_address: str,
        recipient_address: str,
        value: float,
        solvable_utxo: list = None,
        secret_hash: str = None,
    ) -> Optional[BitcoinAtomicSwapTransaction]:
        '''
        Creates atomic swap unsigned transaction object.

        Args:
            sender_address (str): wallet address of the sender
            recipient_address (str): wallet address of the recipient
            value (float): amount to swap
            solvable_utxo (list): optional list of UTXO objects. If None then it will try to find UTXO automatically
                by using the `get_utxo` method.
            secret_hash (str): optional secret hash to be used in transaction. If None then the new hash
                will be generated.

        Returns:
            BitcoinAtomicSwapTransaction, None: unsigned Atomic Swap transaction object or None if something went wrong

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.atomic_swap('msJ2ucZ2NDhpVzsiNE5mGUFzqFDggjBVTM', 'mmJtKA92Mxqfi3XdyGReza69GjhkwAcBN1', 2.4)
            <clove.network.bitcoin.transaction.BitcoinAtomicSwapTransaction at 0x7f989439d630>
        '''
        if not solvable_utxo:
            solvable_utxo = self.get_utxo(sender_address, value)
            if not solvable_utxo:
                logger.error(f'Cannot get UTXO for address {sender_address}')
                return
        transaction = BitcoinAtomicSwapTransaction(self, sender_address,
                                                   recipient_address, value,
                                                   solvable_utxo, secret_hash)
        transaction.create_unsigned_transaction()
        return transaction
Ejemplo n.º 21
0
    def get_balance(cls, wallet_address: str) -> Optional[float]:
        '''
        Returns wallet balance without unconfirmed transactions.

        Args:
            wallet_address (str): wallet address

        Returns:
            float, None: account balance converted from base units or None if something went wrong

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.get_balance('msJ2ucZ2NDhpVzsiNE5mGUFzqFDggjBVTM')
            4.22188744
        '''
        data = clove_req_json(
            f'{cls.blockcypher_url()}/addrs/{wallet_address}/balance')
        if data is None:
            logger.error('Could not get details for address %s in %s network',
                         wallet_address, cls.symbols[0])
            return
        return from_base_units(data['balance'] or data['unconfirmed_balance'])
Ejemplo n.º 22
0
    def capture_messages(self,
                         expected_message_types: list,
                         timeout: int = 20,
                         buf_size: int = 1024,
                         ignore_empty: bool = False) -> list:
        '''
        Method for receiving messages from network nodes.

        Args:
            expected_message_types (list): list of message types to search for
            timeout (int): timeout for waiting for messages
            buf_size (int): buffer size that is going to be used for receiving messages
            ignore_empty (bool): flag for ignoring errors if the message that we're looking for wasn't found.
                Eg. this flag can be set to True when looking for reject messages because the absence of the
                reject message is not an error.

        Returns:
            list, None: list of received messages or None if none or not all expected message types were found

        Example:
            >>> from bitcoin.messages import msg_verack, msg_version
            >>> network.capture_messages([msg_version, msg_verack])
        '''
        deadline = time() + timeout
        found = []
        partial_message = None

        while expected_message_types and time() < deadline:

            try:
                received_data = self.connection.recv(buf_size)
                if partial_message:
                    received_data = partial_message + received_data
            except socket.timeout:
                continue

            if not received_data:
                sleep(0.1)
                continue

            for raw_message in self.split_message(received_data):

                try:
                    message = MsgSerializable.from_bytes(raw_message)
                except (SerializationError, SerializationTruncationError,
                        ValueError):
                    partial_message = raw_message
                    continue

                partial_message = None
                if not message:
                    # unknown message type, skipping
                    continue

                msg_type = type(message)

                if msg_type is msg_ping:
                    logger.debug('Got ping, sending pong.')
                    self.send_pong(message)
                elif msg_type is msg_version:
                    logger.debug('Saving version')
                    self.protocol_version = message

                if msg_type in expected_message_types:
                    found.append(message)
                    expected_message_types.remove(msg_type)
                    logger.debug('Found %s, %s more to catch',
                                 msg_type.command.upper(),
                                 len(expected_message_types))

        if not expected_message_types:
            return found

        if not ignore_empty:
            logger.error('Not all messages could be captured')
Ejemplo n.º 23
0
def create_github_links(line):
    search_base = 'https://api.github.com/search/code?q=op_checklocktimeverify+in:file+repo:'
    repo_info_base = 'https://api.github.com/repos/'

    repo_name = line.strip().split('github.com/')[1]
    repo_name = repo_name.split('/blob/')[0]

    repo_zip_file = f'https://github.com/{repo_name}/archive/master.zip'

    return search_base + repo_name, repo_info_base + repo_name, repo_zip_file


if __name__ == '__main__':
    if get_github_api_token() is None:
        logger.error(
            'GITHUB_API_TOKEN environment variable is required to run this script.'
        )
        exit(1)

    base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
    network_dir = os.path.join(base_dir, 'clove/network/bitcoin_based/')
    files = sorted(glob.glob(network_dir + '*'))

    found = []
    fork = []
    failed = []

    os.makedirs('repos_zip', exist_ok=True)

    for file_path in files:
        if not os.path.isfile(file_path):