def create_transaction(self, op_return_bytes):
        if self.prepared_inputs:
            inputs = self.prepared_inputs
        else:
            spendables = self.connector.get_unspent_outputs(
                self.issuing_address)
            if not spendables:
                error_message = 'No money to spend at address {}'.format(
                    self.issuing_address)
                logging.error(error_message)
                raise InsufficientFundsError(error_message)

            cost = self.transaction_creator.estimate_cost_for_certificate_batch(
                self.tx_cost_constants)
            current_total = 0
            inputs = []
            random.shuffle(spendables)
            for s in spendables:
                inputs.append(s)
                current_total += s.coin_value
                if current_total > cost:
                    break

        tx = self.transaction_creator.create_transaction(
            self.tx_cost_constants, self.issuing_address, inputs,
            op_return_bytes)
        hex_tx = b2h(tx.serialize())
        logging.info('Unsigned hextx=%s', hex_tx)
        prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, inputs)
        return prepared_tx
Exemple #2
0
    def check_balance(self, address, transaction_costs):
        """Check there is enough balance in the wallet. Throws error if funds are lacking"""
        amount_needed = self.calculate_funds_needed(address, transaction_costs)

        if amount_needed > 0:
            error_message = 'Please add {} satoshis to the address {}'.format(
                amount_needed, address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)
Exemple #3
0
    def get_unspent_outputs(self, address):
        unspent_outputs = self.connector.get_unspent_outputs(address)

        if not unspent_outputs:
            error_message = 'No money to spend at address {}'.format(address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)

        unspent_sorted = sorted(unspent_outputs, key=lambda x: hash(x.amount))
        return unspent_sorted
Exemple #4
0
    def check_balance(self, issuing_address, transaction_costs):
        """Check there is enough balance in the wallet."""
        address_balance = self.get_confirmed_balance(issuing_address)
        amount_needed = transaction_costs.difference(address_balance)

        if amount_needed > 0:
            error_message = 'Please add {} satoshis to the address {}'.format(
                amount_needed, issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)
Exemple #5
0
    def ensure_balance(self):
        self.balance = self.connector.get_balance(self.issuing_address)

        transaction_cost = self.tx_cost_constants.get_recommended_max_cost()
        logging.info('Total cost will be %d wei', transaction_cost)

        if transaction_cost > self.balance:
            error_message = 'Please add {} wei to the address {}'.format(
                transaction_cost - self.balance, self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)
    def ensure_balance(self):
        # ensure the issuing address has sufficient balance
        balance = self.connector.get_balance(self.issuing_address)

        transaction_cost = self.transaction_creator.estimate_cost_for_certificate_batch(self.tx_cost_constants)
        logging.info('Total cost will be %d satoshis', transaction_cost)

        if transaction_cost > balance:
            error_message = 'Please add {} satoshis to the address {}'.format(
                transaction_cost - balance, self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)
 def create_transaction(self, blockchain_bytes):
     if self.balance:
         ##it is assumed here that the address has sufficient funds, as the ensure_balance has just been checked
         nonce = self.connector.get_address_nonce(self.issuing_address)
         #Transactions in the first iteration will be send to burn address
         toaddress = '0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead'
         tx = self.transaction_creator.create_transaction(self.tx_cost_constants, self.issuing_address, nonce, toaddress, blockchain_bytes)
         
         prepared_tx = tx
         return prepared_tx
     else:
         raise InsufficientFundsError('Not sufficient ether to spend at: %s', self.issuing_address)
Exemple #8
0
    def ensure_balance(self):
        # testing etherscan api wrapper
        self.balance = self.connector.get_balance(self.issuing_address)

        # for now transaction cost will be a constant: (25000 gas estimate times 20Gwei gasprice) from tx_utils
        # can later be calculated inside EthereumTransaction_creator
        transaction_cost = self.tx_cost_constants.get_recommended_max_cost()
        logging.info('Total cost will be %d wei', transaction_cost)

        if transaction_cost > self.balance:
            error_message = 'Please add {} wei to the address {}'.format(
                transaction_cost - self.balance, self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)
    def create_transactions(self, revocation_address,
                            issuing_transaction_cost):
        """
        Create the batch Bitcoin transaction
        :param revocation_address:
        :param issuing_transaction_cost:
        :return:
        """
        self.tree.make_tree()

        spendables = get_unspent_outputs(self.issuing_address)
        if not spendables:
            error_message = 'No money to spend at address {}'.format(
                self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)

        last_input = spendables[-1]

        op_return_value = unhexlify(self.tree.get_merkle_root())

        tx_outs = self.build_recipient_tx_outs()
        tx_outs.append(
            trx_utils.create_transaction_output(
                revocation_address, issuing_transaction_cost.min_per_output))

        transaction = trx_utils.create_trx(op_return_value,
                                           issuing_transaction_cost,
                                           self.issuing_address, tx_outs,
                                           last_input)

        unsigned_tx_file_name = convert_file_name(
            self.config.unsigned_txs_file_pattern, self.batch_id)
        unsent_tx_file_name = convert_file_name(
            self.config.signed_txs_file_pattern, self.batch_id)
        sent_tx_file_name = convert_file_name(
            self.config.sent_txs_file_pattern, self.batch_id)

        transaction_data = TransactionData(
            uid=self.batch_id,
            tx=transaction,
            tx_input=last_input,
            op_return_value=hexlify(op_return_value),
            unsigned_tx_file_name=unsigned_tx_file_name,
            signed_tx_file_name=unsent_tx_file_name,
            sent_tx_file_name=sent_tx_file_name)

        return [transaction_data]
Exemple #10
0
    def check_balance(self, address, transaction_costs):
        """
        Returns amount needed in wallet to perform transaction(s). A positive return value indicates funds are missing

        :param address:
        :param transaction_costs:
        :return:
        """

        amount_needed = self.check_balance_no_throw(
            address, transaction_costs=transaction_costs)

        if amount_needed > 0:
            error_message = 'Please add {} satoshis to the address {}'.format(
                amount_needed, address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)
Exemple #11
0
    def issue_on_blockchain(self):
        """
        Issue the certificates on the Bitcoin blockchain
        :param revocation_address:
        :return:
        """

        self.tree.make_tree()
        op_return_value_bytes = unhexlify(self.tree.get_merkle_root())
        op_return_value = hexlify(op_return_value_bytes)
        spendables = self.connector.get_unspent_outputs(self.issuing_address)
        if not spendables:
            error_message = 'No money to spend at address {}'.format(self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)

        last_input = spendables[-1]

        tx = self.transaction_handler.create_transaction(last_input, op_return_value_bytes)

        hex_tx = hexlify(tx.serialize())
        logging.info('Unsigned hextx=%s', hex_tx)

        prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, [last_input])
        with FinalizableSigner(self.secure_signer) as signer:
            signed_tx = signer.sign_transaction(prepared_tx)

        # log the actual byte count
        tx_byte_count = tx_utils.get_byte_count(signed_tx)
        logging.info('The actual transaction size is %d bytes', tx_byte_count)

        signed_hextx = signed_tx.as_hex()
        logging.info('Signed hextx=%s', signed_hextx)

        # verify transaction before broadcasting
        tx_utils.verify_transaction(signed_hextx, op_return_value)

        # send tx and persist txid
        tx_id = self.connector.broadcast_tx(signed_tx)
        if tx_id:
            logging.info('Broadcast transaction with txid %s', tx_id)
        else:
            logging.warning(
                'could not broadcast transaction but you can manually do it! signed hextx=%s', signed_hextx)

        return tx_id
Exemple #12
0
    def create_transactions(self, revocation_address):
        """
        Create the batch Bitcoin transaction
        :param revocation_address:
        :return:
        """
        self.tree.make_tree()

        spendables = self.connector.get_unspent_outputs(self.issuing_address)
        if not spendables:
            error_message = 'No money to spend at address {}'.format(
                self.issuing_address)
            logging.error(error_message)
            raise InsufficientFundsError(error_message)

        last_input = spendables[-1]

        op_return_value = unhexlify(self.tree.get_merkle_root())

        tx_outs = self.build_recipient_tx_outs()
        tx_outs.append(
            tx_utils.create_transaction_output(
                revocation_address,
                self.tx_cost_constants.get_minimum_output_coin()))

        transaction = tx_utils.create_trx(op_return_value, self.total,
                                          self.issuing_address, tx_outs,
                                          last_input)

        transaction_data = TransactionData(
            uid=self.batch_id,
            tx=transaction,
            tx_input=last_input,
            op_return_value=hexlify(op_return_value),
            batch_metadata=self.batch_metadata)

        return [transaction_data]
def main(app_config):
    unsigned_certs_dir = app_config.unsigned_certificates_dir
    signed_certs_dir = app_config.signed_certificates_dir
    blockcerts_dir = app_config.blockchain_certificates_dir
    work_dir = app_config.work_dir

    # find certificates to issue
    certificates = helpers.find_certificates_to_process(
        unsigned_certs_dir, signed_certs_dir)
    if not certificates:
        logging.warning('No certificates to process')
        raise NoCertificatesFoundError('No certificates to process')

    certificates, batch_metadata = helpers.prepare_issuance_batch(
        unsigned_certs_dir, signed_certs_dir, work_dir)
    logging.info('Processing %d certificates under work path=%s',
                 len(certificates), work_dir)

    issuing_address = app_config.issuing_address
    revocation_address = app_config.revocation_address

    if app_config.wallet_connector_type == 'blockchain.info':
        wallet_credentials = {
            'wallet_guid': app_config.wallet_guid,
            'wallet_password': app_config.wallet_password,
            'api_key': app_config.api_key,
        }
    else:
        wallet_credentials = {}

    connector = ServiceProviderConnector(app_config.netcode,
                                         app_config.wallet_connector_type,
                                         wallet_credentials)
    path_to_secret = os.path.join(app_config.usb_name, app_config.key_file)

    signer = Signer(
        FileSecretManager(path_to_secret=path_to_secret,
                          disable_safe_mode=app_config.safe_mode))
    tx_constants = TransactionCostConstants(app_config.tx_fee,
                                            app_config.dust_threshold,
                                            app_config.satoshi_per_byte)

    issuer = BatchIssuer(netcode=app_config.netcode,
                         issuing_address=issuing_address,
                         certificates_to_issue=certificates,
                         connector=connector,
                         signer=signer,
                         tx_cost_constants=tx_constants,
                         batch_metadata=batch_metadata)

    issuer.validate_schema()

    # verify signed certs are signed with issuing key
    [
        verify_signature(uid, cert.signed_cert_file_name, issuing_address)
        for uid, cert in certificates.items()
    ]

    logging.info('Hashing signed certificates.')
    issuer.hash_certificates()

    # calculate transaction cost
    transaction_cost = issuer.calculate_cost_for_certificate_batch()

    logging.info('Total cost will be %d satoshis', transaction_cost)

    # ensure the issuing address has sufficient balance
    balance = connector.get_balance(issuing_address)

    if transaction_cost > balance:
        error_message = 'Please add {} satoshis to the address {}'.format(
            transaction_cost - balance, issuing_address)
        logging.error(error_message)
        raise InsufficientFundsError(error_message)

    # issue the certificates on the blockchain
    logging.info('Issuing the certificates on the blockchain')
    issuer.issue_on_blockchain(revocation_address=revocation_address)

    blockcerts_tmp_dir = os.path.join(work_dir,
                                      helpers.BLOCKCHAIN_CERTIFICATES_DIR)
    if not os.path.exists(blockcerts_dir):
        os.makedirs(blockcerts_dir)
    for item in os.listdir(blockcerts_tmp_dir):
        s = os.path.join(blockcerts_tmp_dir, item)
        d = os.path.join(blockcerts_dir, item)
        shutil.copy2(s, d)

    logging.info('Your Blockchain Certificates are in %s', blockcerts_dir)
    return blockcerts_dir
Exemple #14
0
    def issue_on_blockchain(self):
        """
        Issue the certificates on the Bitcoin blockchain
        :param revocation_address:
        :return:
        """

        self.tree.make_tree()
        op_return_value_bytes = unhexlify(self.tree.get_merkle_root())
        op_return_value = hexlify(op_return_value_bytes)

        for attempt_number in range(0, self.max_retry):
            try:
                if self.prepared_inputs:
                    inputs = self.prepared_inputs
                else:
                    spendables = self.connector.get_unspent_outputs(
                        self.secure_signer.issuing_address)
                    if not spendables:
                        error_message = 'No money to spend at address {}'.format(
                            self.secure_signer.issuing_address)
                        logging.error(error_message)
                        raise InsufficientFundsError(error_message)

                    cost = self.transaction_handler.estimate_cost_for_certificate_batch(
                    )
                    current_total = 0
                    inputs = []
                    random.shuffle(spendables)
                    for s in spendables:
                        inputs.append(s)
                        current_total += s.coin_value
                        if current_total > cost:
                            break

                tx = self.transaction_handler.create_transaction(
                    inputs, op_return_value_bytes)
                hex_tx = hexlify(tx.serialize())
                logging.info('Unsigned hextx=%s', hex_tx)

                prepared_tx = tx_utils.prepare_tx_for_signing(hex_tx, inputs)
                with FinalizableSigner(self.secure_signer) as signer:
                    signed_tx = signer.sign_transaction(prepared_tx)

                # log the actual byte count
                tx_byte_count = tx_utils.get_byte_count(signed_tx)
                logging.info('The actual transaction size is %d bytes',
                             tx_byte_count)

                signed_hextx = signed_tx.as_hex()
                logging.info('Signed hextx=%s', signed_hextx)

                # verify transaction before broadcasting
                tx_utils.verify_transaction(signed_hextx, op_return_value)

                # send tx and persist txid
                tx_id = self.connector.broadcast_tx(signed_tx)
                logging.info('Broadcast transaction with txid %s', tx_id)
                return tx_id
            except BroadcastError:
                logging.warning(
                    'Failed broadcast reattempts. Trying to recreate transaction. This is attempt number %d',
                    attempt_number)
        logging.error(
            'All attempts to broadcast failed. Try rerunning issuer.')
        raise BroadcastError(
            'All attempts to broadcast failed. Try rerunning issuer.')
def main(app_config, secure_signer=None):
    unsigned_certs_dir = app_config.unsigned_certificates_dir
    signed_certs_dir = app_config.signed_certificates_dir
    blockchain_certificates_dir = app_config.blockchain_certificates_dir
    work_dir = app_config.work_dir
    v2 = app_config.v2
    issuing_address = app_config.issuing_address
    revocation_address = app_config.revocation_address  # not needed for v2

    certificates, batch_metadata = helpers.prepare_issuance_batch(
        unsigned_certs_dir, signed_certs_dir, blockchain_certificates_dir,
        work_dir)
    logging.info('Processing %d certificates under work path=%s',
                 len(certificates), work_dir)

    logging.info('Signing certificates...')
    if not secure_signer:
        secure_signer = secure_signer_helper.initialize_secure_signer(
            app_config)
    connector = ServiceProviderConnector(app_config.bitcoin_chain)
    tx_constants = TransactionCostConstants(app_config.tx_fee,
                                            app_config.dust_threshold,
                                            app_config.satoshi_per_byte)

    if v2:
        certificate_handler = CertificateV2Handler()
        transaction_handler = TransactionV2Handler(
            tx_cost_constants=tx_constants, issuing_address=issuing_address)
    else:
        certificate_handler = CertificateV1_2Handler()
        transaction_handler = TransactionV1_2Handler(
            tx_cost_constants=tx_constants,
            issuing_address=issuing_address,
            certificates_to_issue=certificates,
            revocation_address=revocation_address)
    certificate_batch_handler = CertificateBatchHandler(
        certificates_to_issue=certificates,
        certificate_handler=certificate_handler)
    issuer = Issuer(issuing_address=issuing_address,
                    connector=connector,
                    secure_signer=secure_signer,
                    certificate_batch_handler=certificate_batch_handler,
                    transaction_handler=transaction_handler)
    transaction_cost = issuer.calculate_cost_for_certificate_batch()
    logging.info('Total cost will be %d satoshis', transaction_cost)

    # ensure the issuing address has sufficient balance
    balance = connector.get_balance(issuing_address)

    if transaction_cost > balance:
        error_message = 'Please add {} satoshis to the address {}'.format(
            transaction_cost - balance, issuing_address)
        logging.error(error_message)
        raise InsufficientFundsError(error_message)

    tx_id = issuer.issue_certificates()

    blockcerts_tmp_dir = os.path.join(work_dir,
                                      helpers.BLOCKCHAIN_CERTIFICATES_DIR)
    if not os.path.exists(blockchain_certificates_dir):
        os.makedirs(blockchain_certificates_dir)
    for item in os.listdir(blockcerts_tmp_dir):
        s = os.path.join(blockcerts_tmp_dir, item)
        d = os.path.join(blockchain_certificates_dir, item)
        shutil.copy2(s, d)

    logging.info('Your Blockchain Certificates are in %s',
                 blockchain_certificates_dir)
    return tx_id