def block_scan(self, start_block, end_block=None, timeout=10, update_latest=False, save_transactions=False, background=False): thread_number = int(random.random() * 10000) if not self.get_available_lock(): scanlog.info('Duplicate blockscan, exiting: %s#%s' % (self.network, thread_number)) count_metrics('scanner.duplicate_blockscanner', {'network': self.network.nickname}) return scanlog.info('Starting blockscan: %s#%s' % (self.network, thread_number)) start = time.time() end = start + timeout next_block = start_block while time.time() < end: elapsed = time.time() - start current_block = self.network.current_block() count_metrics('scanner.blocks_behind', {'network': self.network.nickname}, max(current_block - next_block, 0)) if next_block > current_block: scanlog.info('Ending blockscan#%s [%s]: No more %s blocks!' % ( thread_number, elapsed, self.network)) count_metrics('scanner.end_blockscan', {'network': self.network.nickname, 'reason': 'no_more_blocks'}, elapsed, 'Seconds') self.release_lock() return if end_block and next_block > end_block: scanlog.info('Ending blockscan#%s [%s]: Reached endblock!' % ( thread_number, elapsed)) count_metrics('scanner.end_blockscan', {'network': self.network.nickname, 'reason': 'reached_endblock'}, elapsed, 'Seconds') self.release_lock() return if background: from .tasks import async_process_block async_process_block(self.id, next_block) else: self.process_block(next_block, save_transactions=save_transactions) if update_latest: self.latest_block = next_block self.save() next_block += 1 elapsed = time.time() - start # Mail an admin if we run out of scanblock time scanlog.info('Ending blockscan#%s [%s]: Out of time!' % ( thread_number, elapsed)) count_metrics('scanner.end_blockscan', {'network': self.network.nickname, 'reason': 'out_of_time'}, elapsed, 'Seconds') self.release_lock() sys.exit()
def send_notification(self, subject, body): sent = 0 for email in self.notify_email.split(','): try: send_mail( subject, body, '*****@*****.**', [email.strip()], ) count_metrics('tx.notify_email_success', {'network': self.network.nickname}) sent += 1 except Exception as e: count_metrics('tx.notify_email_error', {'network': self.network.nickname}) return sent
def process_block(self, block_number, save_transactions=False): transactions = list( self.network.driver.find_transactions(block_number)) scanlog.info('Processing block: %s @ %s - %s transactions' % ( self.network, block_number, len(transactions))) if save_transactions: import json fh = open('tests/transactions/block-%s.json' % block_number, 'w+') json.dump(transactions, fh, indent=2) self.process_transactions(transactions) # At the very end count_metrics('scanner.process_block', {'network': self.network.nickname})
def do_lookup(cls, asset, currency='USD'): if asset == 'ETH': url = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest' count_metrics('api.coinmarketcap_get_price') parameters = {'symbol': asset, 'convert': currency} headers = { 'Accepts': 'application/json', 'X-CMC_PRO_API_KEY': settings.CMC_API_KEY } session = Session() session.headers.update(headers) try: response = session.get(url, params=parameters) data = json.loads(response.text) return data['data'][asset]['quote'][currency]['price'] except (ConnectionError, Timeout, TooManyRedirects) as e: print(e)
def process_transactions(self, transactions): from tritium.apps.subscriptions.models import Subscription from tritium.apps.contracts.models import ERC20 count = 0 for tx in transactions: count += 1 tx_timer = time.time() if self.in_watch_cache(tx): scanlog.debug('Found transaction: %s' % tx) find_subscribers = ( models.Q(watched_address__iexact=tx['to']) | models.Q(watched_address__iexact=tx['from']) ) if(tx['isToken']): find_subscribers = ( find_subscribers | models.Q(watched_address__iexact=tx['tokenTo']) ) subscriptions = Subscription.objects.filter(find_subscribers) #count_metrics('scanner.timers.filter_subscriptions', {'network': self.network.nickname}, time.time() - tx_timer, 'Seconds') for subscription in subscriptions: subscription_timer = time.time() subscription.found_transaction(tx) #count_metrics('scanner.timers.found_transaction', {'network': self.network.nickname}, time.time() - subscription_timer, 'Seconds') if tx.get('isToken'): token_timer = time.time() try: ERC20.DISCOVERED_TOKEN(self.network, tx['to']) #count_metrics('scanner.token_discovered', {'network': self.network.nickname}) #count_metrics('scanner.timers.discover_token', {'network': self.network.nickname}, time.time() - token_timer, 'Seconds') except Exception as e: from tritium.apps.errors.models import ErrorLog ErrorLog.WARNING('Error importing token', str(e), transaction=tx['hash'] ) scanlog.error('Error importing token %s' % e) #count_metrics('scanner.token_import_error', {'network': self.network.nickname}) #count_metrics('scanner.timers.discover_token_error', {'network': self.network.nickname}, time.time() - token_timer, 'Seconds') #count_metrics('scanner.timers.whole_process', {'network': self.network.nickname}, time.time() - tx_timer, 'Seconds') count_metrics('scanner.process_transactions', {'network': self.network.nickname}, count)
def found_transaction(self, tx): from tritium.apps.contracts.models import PriceLookup, Contract # Too noisy, getting expensive #log.info('Found transaction: %s' % tx) credits = 0 charges = [] if self.status == 'paused': log.debug('Subscription is paused, skipping transaction') count_metrics('tx.subscription_paused', {'network': self.network.nickname}) return if SubscribedTransaction.objects.filter(tx_hash=tx['hash'], subscription=self): log.debug('Already seen this transaction before, skipping') count_metrics('tx.duplicate_transaction', {'network': self.network.nickname}) return if not 'datetime' in tx: log.error('WHAT THE HELL GUY, NO DATETIME') count_metrics('tx.error_missing_datetime', {'network': self.network.nickname}) return parameters = None if tx['hasData'] and self.specific_contract_calls: contract = Contract.LOOKUP(tx['to']) w3c = contract.get_web3_contract() function = w3c.get_function_by_selector(tx['input'][:10]) if function.fn_name not in self.abi_methods.split(','): log.debug('Not watching this function: %s' % function.fn_name) count_metrics('tx.function_not_watched', {'network': self.network.nickname}) return else: charges.append('Method: %s' % function.fn_name) credits += settings.SPECIFIC_CALLS_CREDIT_COST parameters = { 'abi': function.abi, 'values': w3c.decode_function_input(tx['input'])[1] } else: if self.watch_token_transfers == False: if tx['isToken']: log.debug( 'Its a token transaction and we arent watching tokens, skip' ) count_metrics('tx.tokens_not_watched', {'network': self.network.nickname}) return else: if tx['isToken']: credits += settings.TOKEN_TRANSFERS_CREDIT_COST charges.append('Token Transaction') if self.include_pricing_data: credits += settings.PRICING_DATA_CREDIT_COST charges.append('Pricing Data') price_info = PriceLookup.get_latest('ETH') else: price_info = None stx = SubscribedTransaction.objects.create( subscription=self, created_at=tx['datetime'], block_hash=tx['blockHash'], block_number=tx['blockNumber'], from_address=tx['from'], gas=tx['gas'], gas_price=tx['gasPrice'], tx_hash=tx['hash'], tx_input=tx['input'], nonce=tx['nonce'], to_address=tx['to'], transaction_index=tx['transactionIndex'], value=tx['value'], has_data=tx['hasData'], is_token=tx['isToken'], token_amount=tx.get('tokenAmount', 0), token_to=tx.get('tokenTo', ''), price_lookup=price_info, parameters_json=parameters and json.dumps(parameters) or None) tx.pop('datetime', '') # not serializable output = { 'transaction': stx.serialize(), 'subscription': self.serialize() } if self.notify_url and self.realtime_webhooks: log.debug('Webhook TX Notification to %s' % self.notify_url) try: r = requests.post(self.notify_url, json=output) log.debug('Webhook response: %s' % r.content) count_metrics('tx.notify_webhook_success', {'network': self.network.nickname}) credits += settings.NOTIFICATION_CREDIT_COST charges.append('Webhook') except Exception as e: count_metrics('tx.notify_webhook_error', {'network': self.network.nickname}) if self.notify_email and self.realtime_emails: log.debug('Email TX Notification to %s' % self.notify_email) sent = self.send_notification( '%s: Transaction Received' % self.nickname, json.dumps(output, indent=2)) credits += settings.NOTIFICATION_CREDIT_COST * sent charges.append('Real-time Email') if self.low_balance_warning: balance = self.network.get_balance(self.watched_address) spent = stx.value + stx.gas * stx.gas_price if balance <= spent * 10: self.send_notification( '%s: Low Balance' % self.nickname, 'The balance of address %s is too low to sustain transactions of size %s; %s remaining' % (self.watched_address, spent, balance / 10E18)) reason = ('Transaction [%s]: ' % self.nickname) + ','.join(charges) if not self.user.subtract_credit(credits, reason): self.status = 'paused' self.save()
def get_etherscan_abi(address): count_metrics('api.get_etherscan_abi') url = settings.ETHSCAN_URL + 'module=contract&action=getabi&address=' + address response = requests.get(url) return json.loads(response.content)['result']