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
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)
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
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)
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)
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]
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)
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
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
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