def process_outgoing_transactions(): with CacheLock('process_outgoing_transactions'): update_wallets = [] for ot in OutgoingTransaction.objects.filter(executed_at=None): result = None OutgoingTransaction.objects.filter(id=ot.id).update(executed_at=datetime.datetime.now(), txid=result) db_transaction.commit() try: result = bitcoind.send(ot.to_bitcoinaddress, ot.amount) except jsonrpc.JSONRPCException: raise OutgoingTransaction.objects.filter(id=ot.id).update(txid=result) transaction = bitcoind.gettransaction(result) if Decimal(transaction['fee']) < Decimal(0): wt = ot.wallettransaction_set.all()[0] fee_transaction = WalletTransaction.objects.create( amount=Decimal(transaction['fee']) * Decimal(-1), from_wallet_id=wt.from_wallet_id) update_wallets.append(wt.from_wallet_id) db_transaction.commit() for wid in update_wallets: if getattr(django_settings, "CELERY_ALWAYS_EAGER", False): # Do not do asyncrhonous transaction processing update_wallet_balance(wid) db_transaction.commit() else: update_wallet_balance.delay(wid)
def send_to_address(self, address, amount, description=''): if settings.BITCOIN_DISABLE_OUTGOING: raise Exception("Outgoing transactions disabled! contact support.") address = address.strip() if type(amount) != Decimal: amount = Decimal(amount) amount = amount.quantize(Decimal('0.00000001')) if not is_valid_btc_address(str(address)): raise Exception(_("Not a valid bitcoin address") + ":" + address) if amount <= 0: raise Exception(_("Can't send zero or negative amounts")) # concurrency check with db_transaction.autocommit(): db_transaction.enter_transaction_management() db_transaction.commit() avail = self.total_balance() updated = Wallet.objects.filter(Q(id=self.id)).update(last_balance=avail) if amount > avail: raise Exception(_("Trying to send too much")) new_balance = avail - amount updated = Wallet.objects.filter(Q(id=self.id) & Q(transaction_counter=self.transaction_counter) & Q(last_balance=avail) )\ .update(last_balance=new_balance, transaction_counter=self.transaction_counter+1) if not updated: print "address transaction concurrency:", new_balance, avail, self.transaction_counter, self.last_balance, self.total_balance() raise Exception(_("Concurrency error with transactions. Please try again.")) # concurrency check end bwt = WalletTransaction.objects.create( amount=amount, from_wallet=self, to_bitcoinaddress=address, description=description) try: result = bitcoind.send(address, amount) except jsonrpc.JSONRPCException: bwt.delete() raise self.transaction_counter = self.transaction_counter+1 self.last_balance = new_balance # check if a transaction fee exists, and deduct it from the wallet # TODO: because fee can't be known beforehand, can result in negative wallet balance. # currently isn't much of a issue, but might be in the future, depending of the application transaction = bitcoind.gettransaction(result) fee_transaction = None total_amount = amount if Decimal(transaction['fee']) < Decimal(0): fee_transaction = WalletTransaction.objects.create( amount=Decimal(transaction['fee']) * Decimal(-1), from_wallet=self) total_amount += fee_transaction.amount if settings.BITCOIN_TRANSACTION_SIGNALING: balance_changed.send(sender=self, changed=(Decimal(-1) * total_amount), transaction=bwt) balance_changed_confirmed.send(sender=self, changed=(Decimal(-1) * total_amount), transaction=bwt) return (bwt, fee_transaction)
def send_to_address(self, address, amount, description=''): if settings.BITCOIN_DISABLE_OUTGOING: raise Exception("Outgoing transactions disabled! contact support.") address = address.strip() if type(amount) != Decimal: amount = Decimal(amount) amount = amount.quantize(Decimal('0.00000001')) if not is_valid_btc_address(str(address)): raise Exception(_("Not a valid bitcoin address") + ":" + address) if amount <= 0: raise Exception(_("Can't send zero or negative amounts")) # concurrency check with db_transaction.autocommit(): avail = self.total_balance() updated = Wallet.objects.filter(Q(id=self.id)).update(last_balance=avail) if amount > avail: raise Exception(_("Trying to send too much")) new_balance = avail - amount updated = Wallet.objects.filter(Q(id=self.id) & Q(transaction_counter=self.transaction_counter) & Q(last_balance=avail) )\ .update(last_balance=new_balance, transaction_counter=self.transaction_counter+1) if not updated: print "address transaction concurrency:", new_balance, avail, self.transaction_counter, self.last_balance, self.total_balance() raise Exception(_("Concurrency error with transactions. Please try again.")) # concurrency check end bwt = WalletTransaction.objects.create( amount=amount, from_wallet=self, to_bitcoinaddress=address, description=description) try: result = bitcoind.send(address, amount) except jsonrpc.JSONRPCException: bwt.delete() raise self.transaction_counter = self.transaction_counter+1 self.last_balance = new_balance # check if a transaction fee exists, and deduct it from the wallet # TODO: because fee can't be known beforehand, can result in negative wallet balance. # currently isn't much of a issue, but might be in the future, depending of the application transaction = bitcoind.gettransaction(result) fee_transaction = None total_amount = amount if Decimal(transaction['fee']) < Decimal(0): fee_transaction = WalletTransaction.objects.create( amount=Decimal(transaction['fee']) * Decimal(-1), from_wallet=self) total_amount += fee_transaction.amount if settings.BITCOIN_TRANSACTION_SIGNALING: balance_changed.send(sender=self, changed=(Decimal(-1) * total_amount), transaction=bwt) balance_changed_confirmed.send(sender=self, changed=(Decimal(-1) * total_amount), transaction=bwt) return (bwt, fee_transaction)
def process_outgoing_transactions_group(): if OutgoingTransaction.objects.filter(executed_at=None, expires_at__lte=datetime.datetime.now()).count()>0 or \ OutgoingTransaction.objects.filter(executed_at=None).count()>6: with NonBlockingCacheLock('process_outgoing_transactions'): ots = OutgoingTransaction.objects.filter(executed_at=None).order_by("expires_at")[:7] ots_ids = [ot.id for ot in ots] update_wallets = [] transaction_hash = {} for ot in ots: transaction_hash[ot.to_bitcoinaddress] = float(ot.amount) updated = OutgoingTransaction.objects.filter(id__in=ots_ids, executed_at=None).select_for_update().update(executed_at=datetime.datetime.now()) db_transaction.commit() if updated == len(ots): try: result = bitcoind.sendmany(transaction_hash) except jsonrpc.JSONRPCException as e: if e.error == u"{u'message': u'Insufficient funds', u'code': -4}": u2 = OutgoingTransaction.objects.filter(id__in=ots_ids, under_execution=False ).select_for_update().update(executed_at=None) db_transaction.commit() else: u2 = OutgoingTransaction.objects.filter(id__in=ots_ids, under_execution=False).select_for_update().update(under_execution=True, txid=e.error) # print "error updating", e.error, u2, ots_ids db_transaction.commit() raise OutgoingTransaction.objects.filter(id__in=ots_ids).update(txid=result) db_transaction.commit() transaction = bitcoind.gettransaction(result) if Decimal(transaction['fee']) < Decimal(0): fw = fee_wallet() fee_amount = Decimal(transaction['fee']) * Decimal(-1) orig_fee_transaction = WalletTransaction.objects.create( amount=fee_amount, from_wallet=fw, to_wallet=None) i = 1 for ot_id in ots_ids: wt = WalletTransaction.objects.get(outgoing_transaction__id=ot_id) update_wallets.append(wt.from_wallet_id) fee_transaction = WalletTransaction.objects.create( amount=(fee_amount / Decimal(i)).quantize(Decimal("0.00000001")), from_wallet_id=wt.from_wallet_id, to_wallet=fw, description="fee") i += 1 db_transaction.commit() for wid in update_wallets: update_wallet_balance.delay(wid) elif OutgoingTransaction.objects.filter(executed_at=None).count()>0: next_run_at = OutgoingTransaction.objects.filter(executed_at=None).aggregate(Min('expires_at'))['expires_at__min'] if next_run_at: process_outgoing_transactions.retry( countdown=((next_run_at - datetime.datetime.now()) + datetime.timedelta(seconds=5)).total_seconds() )
def process_outgoing_transactions(): if OutgoingTransaction.objects.filter(executed_at=None, expires_at__lte=datetime.datetime.now()).count()>0 or \ OutgoingTransaction.objects.filter(executed_at=None).count()>6: blockcount = bitcoind.bitcoind_api.getblockcount() with NonBlockingCacheLock('process_outgoing_transactions'): ots_ids = filter_doubles(OutgoingTransaction.objects.filter(executed_at=None).order_by("expires_at")[:15]) ots = OutgoingTransaction.objects.filter(executed_at=None, id__in=ots_ids) update_wallets = [] transaction_hash = {} for ot in ots: transaction_hash[ot.to_bitcoinaddress] = float(ot.amount) updated = OutgoingTransaction.objects.filter(id__in=ots_ids, executed_at=None).select_for_update().update(executed_at=datetime.datetime.now()) if updated == len(ots): try: result = bitcoind.sendmany(transaction_hash) except jsonrpc.JSONRPCException as e: if e.error == u"{u'message': u'Insufficient funds', u'code': -4}" or \ e.error == u"{u'message': u'Insufficient funds', u'code': -6}": u2 = OutgoingTransaction.objects.filter(id__in=ots_ids, under_execution=False ).select_for_update().update(executed_at=None) else: u2 = OutgoingTransaction.objects.filter(id__in=ots_ids, under_execution=False ).select_for_update().update(under_execution=True, txid=e.error) raise OutgoingTransaction.objects.filter(id__in=ots_ids).update(txid=result) transaction = bitcoind.gettransaction(result) if Decimal(transaction['fee']) < Decimal(0): fw = fee_wallet() fee_amount = Decimal(transaction['fee']) * Decimal(-1) orig_fee_transaction = WalletTransaction.objects.create( amount=fee_amount, from_wallet=fw, to_wallet=None) i = 1 for ot_id in ots_ids: wt = WalletTransaction.objects.get(outgoing_transaction__id=ot_id) update_wallets.append(wt.from_wallet_id) fee_transaction = WalletTransaction.objects.create( amount=(fee_amount / Decimal(i)).quantize(Decimal("0.00000001")), from_wallet_id=wt.from_wallet_id, to_wallet=fw, description="fee") i += 1 else: raise Exception("Updated amount not matchinf transaction amount!") for wid in update_wallets: update_wallet_balance.delay(wid)
def process_outgoing_transactions(): if cache.get("process_outgoing_transactions"): print "process ongoing, skipping..." db_transaction.rollback() return if cache.get("wallet_downtime_utc"): db_transaction.rollback() return # try out bitcoind connection print bitcoind.bitcoind_api.getinfo() with NonBlockingCacheLock('process_outgoing_transactions'): update_wallets = [] for ot in OutgoingTransaction.objects.filter(executed_at=None)[:3]: result = None updated = OutgoingTransaction.objects.filter(id=ot.id, executed_at=None, txid=None, under_execution=False).select_for_update().update(executed_at=datetime.datetime.now(), txid=result) db_transaction.commit() if updated: try: result = bitcoind.send(ot.to_bitcoinaddress, ot.amount) updated2 = OutgoingTransaction.objects.filter(id=ot.id, txid=None).select_for_update().update(txid=result) db_transaction.commit() if updated2: transaction = bitcoind.gettransaction(result) if Decimal(transaction['fee']) < Decimal(0): wt = ot.wallettransaction_set.all()[0] fee_transaction = WalletTransaction.objects.create( amount=Decimal(transaction['fee']) * Decimal(-1), from_wallet_id=wt.from_wallet_id) update_wallets.append(wt.from_wallet_id) except jsonrpc.JSONRPCException as e: if e.error == u"{u'message': u'Insufficient funds', u'code': -4}": OutgoingTransaction.objects.filter(id=ot.id, txid=None, under_execution=False).select_for_update().update(executed_at=None) db_transaction.commit() # sleep(10) raise else: OutgoingTransaction.objects.filter(id=ot.id).select_for_update().update(under_execution=True) db_transaction.commit() raise else: raise Exception("Outgoingtransaction can't be updated!") db_transaction.commit() for wid in update_wallets: update_wallet_balance.delay(wid)
def process_outgoing_transactions(): if OutgoingTransaction.objects.filter(executed_at=None, expires_at__lte=datetime.datetime.now()).count()>0 or \ OutgoingTransaction.objects.filter(executed_at=None).count()>6: blockcount = bitcoind.bitcoind_api.getblockcount() with NonBlockingCacheLock('process_outgoing_transactions'): ots_ids = filter_doubles(OutgoingTransaction.objects.filter(executed_at=None).order_by("expires_at")[:15]) ots = OutgoingTransaction.objects.filter(executed_at=None, id__in=ots_ids) update_wallets = [] transaction_hash = {} for ot in ots: transaction_hash[ot.to_bitcoinaddress] = float(ot.amount) updated = OutgoingTransaction.objects.filter(id__in=ots_ids, executed_at=None).select_for_update().update(executed_at=datetime.datetime.now()) if updated == len(ots): try: result = bitcoind.sendmany(transaction_hash) except jsonrpc.JSONRPCException as e: if e.error == u"{u'message': u'Insufficient funds', u'code': -4}" or \ e.error == u"{u'message': u'Insufficient funds', u'code': -6}": u2 = OutgoingTransaction.objects.filter(id__in=ots_ids, under_execution=False ).select_for_update().update(executed_at=None) else: u2 = OutgoingTransaction.objects.filter(id__in=ots_ids, under_execution=False ).select_for_update().update(under_execution=True, txid=e.error) raise OutgoingTransaction.objects.filter(id__in=ots_ids).update(txid=result) transaction = bitcoind.gettransaction(result) if Decimal(transaction['fee']) < Decimal(0): fw = fee_wallet() fee_amount = Decimal(transaction['fee']) * Decimal(-1) orig_fee_transaction = WalletTransaction.objects.create( amount=fee_amount, from_wallet=fw, to_wallet=None) # calculate fair fee wallet = None i = 0 share = (fee_amount / len(ots_ids)).quantize(Decimal("0.00000001")) wallet_transacions = WalletTransaction.objects.filter(outgoing_transaction__id__in=ots_ids).order_by('from_wallet') for wt in wallet_transacions: if (wt.from_wallet != wallet and wallet): update_wallets.append(wallet.id) fee_transaction = WalletTransaction.objects.create( amount=(share * i).quantize(Decimal("0.00000001")), from_wallet_id=wallet.id, to_wallet=fw, description="fee") i = 1 wallet = wt.from_wallet else: i += 1 wallet = wt.from_wallet else: if wallet: update_wallets.append(wallet.id) fee_transaction = WalletTransaction.objects.create( amount=(share * (i - 1) + fee_amount - share * (len(ots_ids) - 1)).quantize(Decimal("0.00000001")), from_wallet_id=wallet.id, to_wallet=fw, description="fee") for wid in update_wallets: update_wallet_balance.delay(wid)