def check_for_replaced_tx(tx_hash, network): """ Get status of the provided transaction hash, and look for a replacement transaction hash. If a replacement exists, return the status and hash of the new transaction """ status, timestamp = get_tx_status(tx_hash, network, timezone.now()) if status in ['pending', 'dropped', 'unknown', '']: new_tx = getReplacedTX(tx_hash) if new_tx: tx_hash = new_tx status, timestamp = get_tx_status(tx_hash, network, timezone.now()) return tx_hash, status, timestamp
def update_tx_status(self): """Updates tx status.""" from dashboard.utils import get_tx_status if self.tx_override: return tx_status, _ = get_tx_status(self.tx_id, self.subscription.network, self.created_on) if self.split_tx_id: split_tx_status, _ = get_tx_status(self.split_tx_id, self.subscription.network, self.created_on) if tx_status != 'pending': self.success = tx_status == 'success' self.tx_cleared = True if self.split_tx_id and split_tx_status != 'pending': self.success = split_tx_status == 'success' self.split_tx_confirmed = True
def update_tx_status(self): """Updates tx status.""" from dashboard.utils import get_tx_status tx_status, tx_time = get_tx_status(self.tx_id, self.subscription.network, self.created_on) if tx_status != 'pending': self.success = tx_status == 'success' self.tx_cleared = True
def process_subscription(subscription, live): is_ready_to_be_processed_db = subscription.get_is_ready_to_be_processed_from_db() logger.info(" - subscription %d", subscription.pk) if is_ready_to_be_processed_db: logger.info(" -- (ready via db) ") are_we_past_next_valid_timestamp = subscription.get_are_we_past_next_valid_timestamp() # FOR DEBUGGING if not live: is_ready_to_be_processed_web3 = subscription.get_is_subscription_ready_from_web3() is_active_web3 = subscription.get_is_active_from_web3() signer = subscription.get_subscription_signer_from_web3() logger.info(" --- DEBUG INFO") logger.info( " --- %s, %s, %s, %s", are_we_past_next_valid_timestamp, is_ready_to_be_processed_web3, is_active_web3, signer, ) if not are_we_past_next_valid_timestamp: logger.info(f" -- ( NOT ready via web3, will be ready on {subscription.get_next_valid_timestamp()}) ") else: logger.info(" -- (ready via web3) ") status = 'failure' txid = None error = "" try: if live: logger.info(" -- *executing* ") while not has_tx_mined(subscription.new_approve_tx_id, subscription.grant.network): time.sleep(SLEEP_TIME) logger.info(f" -- *waiting {SLEEP_TIME} seconds*") txid = subscription.do_execute_subscription_via_web3() logger.info(" -- *waiting for mine* (txid %s) ", txid) while not has_tx_mined(txid, subscription.grant.network): time.sleep(SLEEP_TIME) logger.info(f" -- *waiting {SLEEP_TIME} seconds*") status, __ = get_tx_status(txid, subscription.grant.network, timezone.now()) if status != 'success': error = f"tx status from RPC is {status} not success, txid: {txid}" else: logger.info(" -- *not live, not executing* ") except Exception as e: error = str(e) logger.info(" -- *not live, not executing* ") logger.info(" -- *mined* (status: %s / error: %s) ", status, error) was_success = status == 'success' if live: if not was_success: logger.warning('subscription processing failed') subscription.error = True error_comments = f"{error}\n\ndebug info: {subscription.get_debug_info()}" subscription.subminer_comments = error_comments subscription.save() warn_subscription_failed(subscription) else: logger.info('subscription processing successful') subscription.successful_contribution(txid) subscription.save()
def update_tx_status(self): self.tx_status, self.tx_time = get_tx_status(self.txid, self.network, self.created_on) # Exit if transaction not mined, otherwise continue if self.tx_status != 'success': return bool(self.tx_status) record_ptoken_activity(self.event, self.ptoken, self.profile, self.metadata) self.ptoken.update_token_status()
def check_for_replaced_tx(tx_hash, network, datetime=None, chain='std'): """ Get status of the provided transaction hash, and look for a replacement transaction hash. If a replacement exists, return the status and hash of the new transaction """ from dashboard.utils import get_tx_status if not datetime: datetime = timezone.now() status, timestamp = get_tx_status(tx_hash, network, datetime, chain=chain) if status in ['pending', 'dropped', 'unknown', '']: new_tx = getReplacedTX(tx_hash) if new_tx: tx_hash = new_tx status, timestamp = get_tx_status(tx_hash, network, datetime) return tx_hash, status, timestamp
def process_subscription(subscription, live): is_ready_to_be_processed_db = subscription.get_is_ready_to_be_processed_from_db( ) print(f" - subscription {subscription.pk}") if is_ready_to_be_processed_db: print(" -- (ready via db) ") are_we_past_next_valid_timestamp = subscription.get_are_we_past_next_valid_timestamp( ) # FOR DEBUGGING if not live: is_ready_to_be_processed_web3 = subscription.get_is_subscription_ready_from_web3( ) is_active_web3 = subscription.get_is_active_from_web3() signer = subscription.get_subscription_signer_from_web3() print(" --- DEBUG INFO") print(" --- ", are_we_past_next_valid_timestamp, is_ready_to_be_processed_web3, is_active_web3, signer) if are_we_past_next_valid_timestamp: print(" -- (ready via web3) ") if not live: print(" -- *not live, not executing* ") else: print(" -- *executing* ") status = 'failure' txid = None error = None try: if live: txid = subscription.do_execute_subscription_via_web3() print(f" -- *waiting for mine* (txid {txid}) ") while not has_mined(txid, subscription): time.sleep(10) status, timestamp = get_tx_status( txid, subscription.grant.network, timezone.now()) except Exception as e: error = str(e) print(f" -- *mined* (status: {status} / {error}) ") was_success = status == 'success' if live: if not was_success: warn_subscription_failed(subscription, txid, status, error) else: subscription.successful_contribution() subscription.save()
def update_tx_status(self): self.tx_status, self.tx_time = get_tx_status(self.txid, self.network, self.created_on) if self.tx_status == 'success': metadata = { 'purchase': self.id, 'value_in_token': float(self.amount), 'token_name': self.ptoken.token_symbol, 'from_user': self.ptoken.token_owner_profile.handle, 'holder_user': self.ptoken.token_owner_profile.handle } record_ptoken_activity('buy_ptoken', self.ptoken, self.token_holder_profile, metadata) self.ptoken.update_token_status() self.ptoken.update_user_balance(self.token_holder_profile, self.token_holder_address) return bool(self.tx_status)
def update_tx_status(self): self.tx_status, self.tx_time = get_tx_status(self.txid, self.network, self.created_on) if self.redemption_state == 'waiting_complete': self.ptoken.update_token_status() self.ptoken.update_user_balance(self.redemption_requester, self.redemption_requester_address) if self.tx_status == 'success': metadata = {'redemption': self.id} record_ptoken_activity('complete_redemption_ptoken', self.ptoken, self.redemption_requester, metadata, self) send_ptoken_redemption_complete_for_requester(self.redemption_requester, self.ptoken, self) send_ptoken_redemption_complete_for_owner(self.redemption_requester, self.ptoken, self) self.redemption_state = 'completed' elif self.tx_status in ['error', 'unknown', 'dropped']: self.redemption_state = 'accepted' return bool(self.tx_status)
def update_tx_status(self): self.tx_status, self.tx_time = get_tx_status(self.txid, self.network, self.created_on) # Exit if transaction not mined, otherwise continue if (self.tx_status != 'success'): return bool(self.tx_status) # Get token address from event logs if (self.token_address == "0x0"): web3 = get_web3(self.network) receipt = web3.eth.getTransactionReceipt(self.txid) contract = web3.eth.contract(Web3.toChecksumAddress(FACTORY_ADDRESS), abi=ptoken_factory_abi) logs = contract.events.NewPToken().processReceipt(receipt) self.token_address = logs[0].args.token record_ptoken_activity('create_ptoken', self, self.token_owner_profile) send_personal_token_created(self.token_owner_profile, self) self.update_token_status()
def handle(self, *args, **options): # setup payment_threshold_usd = 1 network = 'mainnet' if not settings.DEBUG else 'rinkeby' from_address = settings.MINICLR_ADDRESS from_pk = settings.MINICLR_PRIVATE_KEY DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f' if network == 'mainnet' else '0x8f2e097e79b1c51be9cba42658862f0192c3e487' # find a round that has recently expired minutes_ago = options['minutes_ago'] cursor_time = timezone.now() - timezone.timedelta(minutes=minutes_ago) mr = MatchRound.objects.filter(valid_from__lt=cursor_time, valid_to__gt=cursor_time, valid_to__lt=timezone.now()).first() if options['round_number']: mr = MatchRound.objects.get(number=options['round_number']) if not mr: print( f'No Match Round Found that ended between {cursor_time} <> {timezone.now()}' ) return print(mr) # finalize rankings if options['what'] == 'finalize': rankings = mr.ranking.filter(final=False, paid=False).order_by('-match_total') print(rankings.count(), "to finalize") for ranking in rankings: ranking.final = True ranking.save() print(rankings.count(), " finalied") # payout rankings (round must be finalized first) if options['what'] == 'payout': rankings = mr.ranking.filter(final=True, paid=False).order_by('-match_total') print(rankings.count(), " to pay") w3 = get_web3(network) for ranking in rankings: # figure out amount_owed profile = ranking.profile owed_rankings = profile.match_rankings.filter(final=True, paid=False) amount_owed = sum( owed_rankings.values_list('match_total', flat=True)) print( f"paying {ranking.profile.handle} who is owed {amount_owed} ({ranking.match_total} from this round)" ) # validate error = None if amount_owed < payment_threshold_usd: error = ("- less than amount owed; continue") address = profile.preferred_payout_address if not address: error = ("- address not on file") if error: print(error) ranking.payout_tx_status = error ranking.save() continue # issue payment contract = w3.eth.contract(Web3.toChecksumAddress(DAI_ADDRESS), abi=abi) address = Web3.toChecksumAddress(address) amount = int(amount_owed * 10**18) tx = contract.functions.transfer( address, amount ).buildTransaction({ 'nonce': w3.eth.getTransactionCount(from_address), 'gas': 100000, 'gasPrice': int( float(recommend_min_gas_price_to_confirm_in_time(1)) * 10**9 * 1.4) }) signed = w3.eth.account.signTransaction(tx, from_pk) tx_id = w3.eth.sendRawTransaction(signed.rawTransaction).hex() print("paid via", tx_id) # wait for tx to clear while not has_tx_mined(tx_id, network): time.sleep(1) ranking.payout_tx_status, ranking.payout_tx_issued = get_tx_status( tx_id, network, timezone.now()) ranking.paid = True ranking.payout_txid = tx_id ranking.save() for other_ranking in owed_rankings: other_ranking.paid = True other_ranking.payout_txid = ranking.payout_txid other_ranking.payout_tx_issued = ranking.payout_tx_issued other_ranking.payout_tx_status = ranking.payout_tx_status other_ranking.save() # create earning object from_profile = Profile.objects.get(handle='gitcoinbot') Earning.objects.update_or_create( source_type=ContentType.objects.get(app_label='townsquare', model='matchranking'), source_id=ranking.pk, defaults={ "created_on": ranking.created_on, "org_profile": None, "from_profile": from_profile, "to_profile": ranking.profile, "value_usd": amount_owed, "url": 'https://gitcoin.co/#clr', "network": network, }) Activity.objects.create(created_on=timezone.now(), profile=ranking.profile, activity_type='mini_clr_payout', metadata={ "amount": float(amount_owed), "number": int(mr.number), "mr_pk": int(mr.pk), "round_description": f"Mini CLR Round {mr.number}" }) from marketing.mails import match_distribution match_distribution(ranking) print("paid ", ranking) time.sleep(30) # announce finalists (round must be finalized first) from_profile = Profile.objects.get(handle='gitcoinbot') if options['what'] == 'announce': copy = f"Mini CLR Round {mr.number} Winners:<BR>" rankings = mr.ranking.filter( final=True).order_by('-match_total')[0:10] print(rankings.count(), " to announce") for ranking in rankings: profile_link = f"<a href=/{ranking.profile}>@{ranking.profile}</a>" copy += f" - {profile_link} was ranked <strong>#{ranking.number}</strong>. <BR>" metadata = { 'copy': copy, } Activity.objects.create( created_on=timezone.now(), profile=from_profile, activity_type='consolidated_mini_clr_payout', metadata=metadata)
def process_subscription(subscription, live): is_ready_to_be_processed_db = subscription.get_is_ready_to_be_processed_from_db() logger.info(" - subscription %d", subscription.pk) if is_ready_to_be_processed_db: logger.info(" -- (ready via db) ") are_we_past_next_valid_timestamp = subscription.get_are_we_past_next_valid_timestamp() has_approve_tx_mined = has_tx_mined(subscription.new_approve_tx_id, subscription.grant.network) # FOR DEBUGGING if not live: is_ready_to_be_processed_web3 = subscription.get_are_we_past_next_valid_timestamp() is_active_web3 = subscription.get_is_active_from_web3() signer = subscription.get_subscription_signer_from_web3() logger.info(" --- DEBUG INFO") logger.info( " --- %s, %s, %s, %s", are_we_past_next_valid_timestamp, is_ready_to_be_processed_web3, is_active_web3, signer, ) if not are_we_past_next_valid_timestamp: logger.info(f" -- ( NOT ready via web3, will be ready on {subscription.get_next_valid_timestamp()}) ") elif not has_approve_tx_mined: logger.info(f" -- ( NOT ready via approve tx, will be ready when {subscription.new_approve_tx_id} mines) ") else: if subscription.contributor_signature == "onetime": subscription.error = True subscription.subminer_comments = "One time subscription" subscription.save() logger.info('skipping one time subscription: %s' % subscription.id) return web3_hash_arguments = subscription.get_subscription_hash_arguments() if web3_hash_arguments['periodSeconds'] < METATX_FREE_INTERVAL_SECONDS and web3_hash_arguments['gasPrice'] <= METATX_GAS_PRICE_THRESHOLD: subscription.error = True subscription.subminer_comments = "Gas price was too low to process" subscription.save() warn_subscription_failed(subscription) return logger.info(" -- (ready via web3) ") status = 'failure' txid = None error = "" try: if live: logger.info(" -- *executing* ") txid = subscription.do_execute_subscription_via_web3() logger.info(" -- *waiting for mine* (txid %s) ", txid) override = False counter = 0 while not has_tx_mined(txid, subscription.grant.network) and not override: time.sleep(SLEEP_TIME) logger.info(f" -- *waiting {SLEEP_TIME} seconds for {txid} to mine*") counter += 1 if counter > MAX_COUNTER: override = True # force the subminer to continue on; this tx is taking too long. # an admin will have to look at this later and determine what went wrong # KO 2019/02/06 status, __ = get_tx_status(txid, subscription.grant.network, timezone.now()) if status != 'success': error = f"tx status from RPC is {status} not success, txid: {txid}" else: logger.info(" -- *not live, not executing* ") except Exception as e: error = str(e) logger.info(" -- *not live, not executing* ") logger.info(" -- *mined* (status: %s / error: %s) ", status, error) was_success = status == 'success' if live: if not was_success: logger.warning('subscription processing failed') subscription.error = True error_comments = f"{error}\n\ndebug info: {subscription.get_debug_info()}" subscription.subminer_comments = error_comments subscription.save() grant = subscription.grant grant.updateActiveSubscriptions() grant.save() warn_subscription_failed(subscription) else: logger.info('subscription processing successful') subscription.successful_contribution(txid) subscription.save()
def grants_transaction_validator(contribution, w3): # To facilitate testing on Rinkeby, we pass in a web3 instance instead of using the mainnet # instance defined at the top of this file tx_list = [contribution.tx_id, contribution.split_tx_id] network = contribution.subscription.network token_transfer = {} txns = [] validation = {'passed': False, 'comment': 'Default'} token_originators = [] amounts = [ contribution.subscription.amount_per_period_minus_gas_price, contribution.subscription.amount_per_period ] for tx in tx_list: if not tx: continue # check for dropped and replaced txn status, timestamp = get_tx_status(tx, network, timezone.now()) maybeprint(120, round(time.time(), 2)) if status in ['pending', 'dropped', 'unknown', '']: new_tx = getReplacedTX(tx) if new_tx: tx = new_tx status, timestamp = get_tx_status(tx, network, timezone.now()) maybeprint(127, round(time.time(), 2)) # check for txfrs if status == 'success': # check if it was an ETH transaction maybeprint(132, round(time.time(), 2)) transaction_receipt = w3.eth.getTransactionReceipt(tx) from_address = transaction_receipt['from'] # todo save back to the txn if needed? if (transaction_receipt != None and transaction_receipt.cumulativeGasUsed >= 2100): maybeprint(138, round(time.time(), 2)) transaction_hash = transaction_receipt.transactionHash.hex() transaction = w3.eth.getTransaction(transaction_hash) if transaction.value > 0.001: recipient_address = Web3.toChecksumAddress( contribution.subscription.grant.admin_address) transfer = get_token_originators(recipient_address, '0x0', from_address=from_address, return_what='transfers', tx_id=tx, amounts=amounts) if not transfer: transfer = get_token_originators( recipient_address, '0x0', from_address=from_address, return_what='transfers', tx_id=tx) if transfer: token_transfer = transfer maybeprint(148, round(time.time(), 2)) if not token_originators: token_originators = get_token_originators( from_address, '0x0', from_address=None, return_what='originators') maybeprint(150, round(time.time(), 2)) # check if it was an ERC20 transaction if contribution.subscription.contributor_address and \ contribution.subscription.grant.admin_address and \ contribution.subscription.token_address: from_address = Web3.toChecksumAddress( contribution.subscription.contributor_address) recipient_address = Web3.toChecksumAddress( contribution.subscription.grant.admin_address) token_address = Web3.toChecksumAddress( contribution.subscription.token_address) maybeprint(160, round(time.time(), 2)) # get token transfers if not token_transfer: transfers = get_token_originators( recipient_address, token_address, from_address=from_address, return_what='transfers', tx_id=tx, amounts=amounts) if transfers: token_transfer = transfers maybeprint(169, round(time.time(), 2)) if not token_originators: token_originators = get_token_originators( from_address, token_address, from_address=None, return_what='originators') maybeprint(170, round(time.time(), 2)) # log transaction and and any xfr txns.append({ 'id': tx, 'status': status, }) if not token_transfer: transaction_receipt = w3.eth.getTransactionReceipt(tx) is_bulk_checkout = transaction_receipt['to'].lower( ) == "0x7d655c57f71464B6f83811C55D84009Cd9f5221C".lower() if is_bulk_checkout: validation['comment'] = "Bulk checkout" validation['passed'] = transaction_receipt['status'] == 1 else: validation['comment'] = "No Transfers Occured" validation['passed'] = False else: if token_transfer[ 'token_name'] != contribution.subscription.token_symbol: validation[ 'comment'] = f"Tokens do not match, {token_transfer['token_name']} != {contribution.subscription.token_symbol}" validation['passed'] = False from_address = Web3.toChecksumAddress( contribution.subscription.contributor_address) recipient_address = Web3.toChecksumAddress( contribution.subscription.grant.admin_address) token_address = Web3.toChecksumAddress( contribution.subscription.token_address) _transfers = get_token_originators(recipient_address, token_address, from_address=from_address, return_what='transfers', tx_id=tx, amounts=amounts) failsafe = _transfers[ 'token_name'] == contribution.subscription.token_symbol if failsafe: validation[ 'comment'] = f"Token Transfer Passed on the second try" validation['passed'] = True token_transfer = _transfers else: delta1 = float(token_transfer['token_amount_decimal']) - float( contribution.subscription.amount_per_period_minus_gas_price) delta2 = float(token_transfer['token_amount_decimal']) - float( contribution.subscription.amount_per_period) threshold = float( float( abs(contribution.subscription. amount_per_period_minus_gas_price)) * float(validation_threshold_pct)) validation['passed'] = ( abs(delta1) <= threshold or abs(delta2) <= threshold) or ( abs(delta1) <= validation_threshold_total or abs(delta2) <= validation_threshold_total) validation[ 'comment'] = f"Transfer Amount is off by {round(delta1, 2)} / {round(delta2, 2)}" return { 'contribution': { 'pk': contribution.pk, 'amount_per_period_to_gitcoin': contribution.subscription.amount_per_period_to_gitcoin, 'amount_per_period_to_grantee': contribution.subscription.amount_per_period_minus_gas_price, 'from': contribution.subscription.contributor_address, 'to': contribution.subscription.grant.admin_address, }, 'validation': validation, 'transfers': token_transfer, 'originator': token_originators, 'txns': txns, }
def grants_transaction_validator(contribution): tx_list = [contribution.tx_id, contribution.split_tx_id] network = contribution.subscription.network token_transfer = {} txns = [] validation = { 'passed': False, 'comment': 'Default' } token_originators = [] for tx in tx_list: if not tx: continue # check for dropped and replaced txn status, timestamp = get_tx_status(tx, network, timezone.now()) maybeprint(120, round(time.time(),2)) if status in ['pending', 'dropped', 'unknown', '']: new_tx = getReplacedTX(tx) if new_tx: tx = new_tx status, timestamp = get_tx_status(tx, network, timezone.now()) maybeprint(127, round(time.time(),2)) # check for txfrs if status == 'success': # check if it was an ETH transaction maybeprint(132, round(time.time(),2)) transaction_receipt = w3.eth.getTransactionReceipt(tx) from_address = transaction_receipt['from'] # todo save back to the txn if needed? if (transaction_receipt != None and transaction_receipt.cumulativeGasUsed >= 2100): maybeprint(138, round(time.time(),2)) transaction_hash = transaction_receipt.transactionHash.hex() transaction = check_transaction(transaction_hash) if transaction.value > 0.001: token_transfer = { 'to': transaction.to, 'token_name': 'ETH', 'token_address': '0x0', 'token_amount_int': Decimal(transaction.value), 'token_amount_decimal': Decimal(transaction.value / 10 **18), 'decimals': 18, } maybeprint(148, round(time.time(),2)) if not token_originators: token_originators = get_token_originators(from_address, '0x0', from_address=None, return_what='originators') maybeprint(150, round(time.time(),2)) # check if it was an ERC20 transaction if contribution.subscription.contributor_address and \ contribution.subscription.grant.admin_address and \ contribution.subscription.token_address: from_address = Web3.toChecksumAddress(contribution.subscription.contributor_address) recipient_address = Web3.toChecksumAddress(contribution.subscription.grant.admin_address) token_address = Web3.toChecksumAddress(contribution.subscription.token_address) maybeprint(160, round(time.time(),2)) # get token transfers if not token_transfer: transfers = get_token_originators(recipient_address, token_address, from_address=from_address, return_what='transfers') if transfers: token_transfer = transfers maybeprint(169, round(time.time(),2)) if not token_originators: token_originators = get_token_originators(from_address, token_address, from_address=None, return_what='originators') maybeprint(170, round(time.time(),2)) # log transaction and and any xfr txns.append({ 'id': tx, 'status': status, }) if not token_transfer: validation['comment'] = "No Transfers Occured" validation['passed'] = False else: if token_transfer['token_name'] != contribution.subscription.token_symbol: validation['comment'] = f"Tokens do not match, {token_transfer['token_name']} != {contribution.subscription.token_symbol}" validation['passed'] = False else: delta = Decimal(token_transfer['token_amount_decimal']) - Decimal(contribution.subscription.amount_per_period_minus_gas_price) # TODO what about gitcoin transfers validation['comment'] = f"Transfer Amount is off by {round(delta, 2)}" validation['passed'] = abs(delta) <= 0.01 return { 'contribution': { 'pk': contribution.pk, 'amount_per_period_to_gitcoin': contribution.subscription.amount_per_period_to_gitcoin, 'amount_per_period_to_grantee': contribution.subscription.amount_per_period_minus_gas_price, 'from': contribution.subscription.contributor_address, 'to': contribution.subscription.grant.admin_address, }, 'validation': validation, 'transfers': token_transfer, 'originator': token_originators, 'txns': txns, }
def process_subscription(subscription, live): is_ready_to_be_processed_db = subscription.get_is_ready_to_be_processed_from_db( ) logger.info(" - subscription %d", subscription.pk) if is_ready_to_be_processed_db: logger.info(" -- (ready via db) ") are_we_past_next_valid_timestamp = subscription.get_are_we_past_next_valid_timestamp( ) # FOR DEBUGGING if not live: is_ready_to_be_processed_web3 = subscription.get_is_subscription_ready_from_web3( ) is_active_web3 = subscription.get_is_active_from_web3() signer = subscription.get_subscription_signer_from_web3() logger.info(" --- DEBUG INFO") logger.info( " --- %s, %s, %s, %s", are_we_past_next_valid_timestamp, is_ready_to_be_processed_web3, is_active_web3, signer, ) if not are_we_past_next_valid_timestamp: logger.info( f" -- ( NOT ready via web3, will be ready on {subscription.get_next_valid_timestamp()}) " ) else: logger.info(" -- (ready via web3) ") status = 'failure' txid = None error = "" try: if live: logger.info(" -- *executing* ") counter = 0 while not has_tx_mined(subscription.new_approve_tx_id, subscription.grant.network): time.sleep(SLEEP_TIME) logger.info( f" -- *waiting {SLEEP_TIME} seconds for {subscription.new_approve_tx_id} to mine*" ) counter += 1 if counter > MAX_COUNTER: raise Exception( f"waited more than {MAX_COUNTER} times for tx to mine" ) txid = subscription.do_execute_subscription_via_web3() logger.info(" -- *waiting for mine* (txid %s) ", txid) override = False counter = 0 while not has_tx_mined( txid, subscription.grant.network) and not override: time.sleep(SLEEP_TIME) logger.info( f" -- *waiting {SLEEP_TIME} seconds for {txid} to mine*" ) counter += 1 if counter > MAX_COUNTER: override = True # force the subminer to continue on; this tx is taking too long. # an admin will have to look at this later and determine what went wrong # KO 2019/02/06 status, __ = get_tx_status(txid, subscription.grant.network, timezone.now()) if status != 'success': error = f"tx status from RPC is {status} not success, txid: {txid}" else: logger.info(" -- *not live, not executing* ") except Exception as e: error = str(e) logger.info(" -- *not live, not executing* ") logger.info(" -- *mined* (status: %s / error: %s) ", status, error) was_success = status == 'success' if live: if not was_success: logger.warning('subscription processing failed') subscription.error = True error_comments = f"{error}\n\ndebug info: {subscription.get_debug_info()}" subscription.subminer_comments = error_comments subscription.save() warn_subscription_failed(subscription) else: logger.info('subscription processing successful') subscription.successful_contribution(txid) subscription.save()
def handle(self, *args, **options): # setup minutes_ago = 10 if not settings.DEBUG else 40 payment_threshold_usd = 1 network = 'mainnet' if not settings.DEBUG else 'rinkeby' from_address = settings.MINICLR_ADDRESS from_pk = settings.MINICLR_PRIVATE_KEY DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f' if network == 'mainnet' else '0x8f2e097e79b1c51be9cba42658862f0192c3e487' provider = settings.WEB3_HTTP_PROVIDER if network == 'mainnet' else "https://rinkeby.infura.io/" # find a round that has recently expired cursor_time = timezone.now() - timezone.timedelta(minutes=minutes_ago) mr = MatchRound.objects.filter(valid_from__lt=cursor_time, valid_to__gt=cursor_time, valid_to__lt=timezone.now()).first() if not mr: print( f'No Match Round Found that ended between {cursor_time} <> {timezone.now()}' ) return print(mr) # finalize rankings rankings = mr.ranking.filter(final=False, paid=False).order_by('number') print(rankings.count()) for ranking in rankings: ranking.final = True ranking.save() # payout rankings rankings = mr.ranking.filter(final=True, paid=False).order_by('number') print(rankings.count()) w3 = Web3(HTTPProvider(provider)) for ranking in rankings: print(ranking) # figure out amount_owed profile = ranking.profile owed_rankings = profile.match_rankings.filter(final=True, paid=False) amount_owed = sum( owed_rankings.values_list('match_total', flat=True)) # validate error = None if amount_owed < payment_threshold_usd: error = ("- less than amount owed; continue") address = profile.preferred_payout_address if not address: error = ("- address not on file") if error: ranking.payout_tx_status = error ranking.save() continue # issue payment contract = w3.eth.contract(Web3.toChecksumAddress(DAI_ADDRESS), abi=abi) address = Web3.toChecksumAddress(address) amount = int(amount_owed * 10**18) tx = contract.functions.transfer(address, amount).buildTransaction( { 'nonce': w3.eth.getTransactionCount(from_address), 'gas': 100000, 'gasPrice': recommend_min_gas_price_to_confirm_in_time(1) * 10**9 }) signed = w3.eth.account.signTransaction(tx, from_pk) tx_id = w3.eth.sendRawTransaction(signed.rawTransaction).hex() # wait for tx to clear while not has_tx_mined(tx_id, network): time.sleep(1) ranking.payout_tx_status, ranking.payout_tx_issued = get_tx_status( tx_id, network, timezone.now()) ranking.paid = True ranking.payout_txid = tx_id ranking.save() for other_ranking in owed_rankings: other_ranking.paid = True other_ranking.payout_txid = ranking.payout_txid other_ranking.payout_tx_issued = ranking.payout_tx_issued other_ranking.payout_tx_status = ranking.payout_tx_status other_ranking.save() # create earning object from dashboard.models import Earning, Profile, Activity from django.contrib.contenttypes.models import ContentType from_profile = Profile.objects.get(handle='gitcoinbot') Earning.objects.update_or_create( source_type=ContentType.objects.get(app_label='townsquare', model='matchranking'), source_id=ranking.pk, defaults={ "created_on": ranking.created_on, "org_profile": None, "from_profile": from_profile, "to_profile": ranking.profile, "value_usd": amount_owed, "url": 'https://gitcoin.co/#clr', "network": network, }) Activity.objects.create(created_on=timezone.now(), profile=ranking.profile, activity_type='mini_clr_payout', metadata={ "amount": amount_owed, }) from marketing.mails import match_distribution match_distribution(ranking)