def create_payment_report(self, nb_failed, nb_injected, payment_logs, payment_cycle, total_attempts): logger.info("Processing completed for {} payment items{}.".format( len(payment_logs), ", {} failed".format(nb_failed) if nb_failed > 0 else "")) report_file = payment_report_file_path(self.payments_dir, payment_cycle, nb_failed) CsvPaymentFileParser().write(report_file, payment_logs) logger.info("Payment report is created at '{}'".format(report_file)) for pl in payment_logs: logger.debug( "Payment done for address %s type %s amount {:>8.2f} paid %s". format(pl.amount / MUTEZ), pl.address, pl.type, pl.paid) if self.publish_stats and not self.dry_run and ( not self.args or is_mainnet(self.args.network)): stats_dict = self.create_stats_dict(nb_failed, nb_injected, payment_cycle, payment_logs, total_attempts) # publish stat_publish(stats_dict) return report_file
def test_retry_failed_payments(self): payment_queue = queue.Queue(100) retry_producer = RetryProducer(payment_queue, _DummyRpcRewardApi(), _TestPaymentProducer(), TEST_REPORT_TEMP_DIR) retry_producer.retry_failed_payments() self.assertEqual(1, len(payment_queue.queue)) payment_batch = payment_queue.get() self.assertEqual(10, payment_batch.cycle) self.assertEqual(31, len(payment_batch.batch)) self.assertEqual(5, len([row for row in payment_batch.batch if row.paid == PaymentStatus.FAIL])) nw = dict({'BLOCK_TIME_IN_SEC': 64}) payment_consumer = self.create_consumer(nw, payment_queue) payment_consumer._consume_batch(payment_batch) success_report = payment_report_file_path(TEST_REPORT_TEMP_DIR, 10, 0) self.assertTrue(os.path.isfile(success_report)) success_report_rows = CsvPaymentFileParser().parse(success_report, 10) nb_success = len([row for row in success_report_rows if row.paid == PaymentStatus.PAID]) nb_hash_xxx_op_hash = len([row for row in success_report_rows if row.hash == 'xxx_op_hash']) self.assertEqual(31, nb_success) self.assertEqual(5, nb_hash_xxx_op_hash)
def test_retry_failed_payments(self): """This is a test about retrying failed operations. Input is a past payment report which contains 31 payment items, 26 of them were successful and 5 of them were failed. The final report should report 31 successful transactions. """ payment_queue = queue.Queue(100) retry_producer = RetryProducer( payment_queue, _DummyRpcRewardApi(), _TestPaymentProducer(), TEST_REPORT_TEMP_DIR, 10, ) retry_producer.retry_failed_payments() self.assertEqual(1, len(payment_queue.queue)) payment_batch = payment_queue.get() self.assertEqual(10, payment_batch.cycle) self.assertEqual(31, len(payment_batch.batch)) self.assertEqual( 5, len([ row for row in payment_batch.batch if row.paid == PaymentStatus.FAIL ]), ) nw = dict({"MINIMAL_BLOCK_DELAY": 30}) payment_consumer = self.create_consumer(nw, payment_queue) payment_consumer._consume_batch(payment_batch) success_report = get_payment_report_file_path(TEST_REPORT_TEMP_DIR, 10, 0) self.assertTrue(os.path.isfile(success_report)) success_report_rows = CsvPaymentFileParser().parse(success_report, 10) success_count = len([row for row in success_report_rows]) hash_xxx_op_count = len( [row for row in success_report_rows if row.hash == "xxx_op_hash"]) failed_reports_count = len([ file for file in os.listdir( os.path.join(TEST_REPORT_TEMP_DIR, "failed")) if os.path.isfile(file) ]) # Success is defined when the transactions are saved in the done folder self.assertEqual(31, success_count) self.assertEqual(5, hash_xxx_op_count) self.assertEqual(0, failed_reports_count)
def create_payment_report(self, nb_failed, payment_logs, payment_cycle, already_paid_items): logger.info("Processing completed for {} payment items{}.".format( len(payment_logs), ", {} failed".format(nb_failed) if nb_failed > 0 else "", )) logger.debug("Adding {} already paid items to the report".format( len(already_paid_items))) payouts = already_paid_items + payment_logs successful_payouts = [ payout for payout in payouts if payout.paid != PaymentStatus.FAIL ] unsuccessful_payouts = [ payout for payout in payouts if payout.paid == PaymentStatus.FAIL ] report_file = get_payment_report_file_path(self.payments_dir, payment_cycle, 0) CsvPaymentFileParser().write(report_file, successful_payouts) logger.info("Payment report is created at '{}'".format(report_file)) if nb_failed > 0: report_file = get_payment_report_file_path(self.payments_dir, payment_cycle, nb_failed) CsvPaymentFileParser().write(report_file, unsuccessful_payouts) logger.info( "Payment report is created at '{}'".format(report_file)) for pl in payment_logs: logger.debug( "Payment done for address {:s} type {:s} amount {:<,d} mutez paid {:s}" .format(pl.address, pl.type, pl.adjusted_amount, pl.paid)) return report_file
def create_payment_report(self, nb_failed, payment_logs, payment_cycle): logger.info("Processing completed for {} payment items{}." .format(len(payment_logs), ", {} failed".format(nb_failed) if nb_failed > 0 else "")) report_file = payment_report_file_path(self.payments_dir, payment_cycle, nb_failed) CsvPaymentFileParser().write(report_file, payment_logs) logger.info("Payment report is created at '{}'".format(report_file)) for pl in payment_logs: logger.debug("Payment done for address {:s} type {:s} amount {:>10.6f} paid {:s}" .format(pl.address, pl.type, pl.amount / MUTEZ, pl.paid)) return report_file
def retry_failed_payments(self, retry_injected=False): logger.debug("retry_failed_payments started") # 1 - list csv files under payments/failed directory # absolute path of csv files found under payments_root/failed directory failed_payments_dir = get_failed_payments_dir(self.payments_root) payment_reports_failed = [ os.path.join(failed_payments_dir, x) for x in os.listdir(failed_payments_dir) if x.endswith('.csv') ] if payment_reports_failed: payment_reports_failed = sorted( payment_reports_failed, key=lambda x: int(os.path.splitext(os.path.basename(x))[0])) logger.debug("Failed payment files found are: '{}'".format( ",".join(payment_reports_failed))) else: logger.debug( "No failed payment files found under directory '{}'".format( failed_payments_dir)) # 2- for each csv file with name csv_report.csv for payment_failed_report_file in payment_reports_failed: logger.info("Working on failed payment file {}".format( payment_failed_report_file)) # 2.1 - if there is a file csv_report.csv under payments/done, it means payment is already done if os.path.isfile( payment_failed_report_file.replace(PAYMENT_FAILED_DIR, PAYMENT_DONE_DIR)): # remove payments/failed/csv_report.csv os.remove(payment_failed_report_file) logger.info( "Payment for failed payment {} is already done. Removing.". format(payment_failed_report_file)) # remove payments/failed/csv_report.csv.BUSY # if there is a busy failed payment report file, remove it. remove_busy_file(payment_failed_report_file) # do not double pay continue # 2.2 - if queue is full, wait for sometime # make sure the queue is not full while self.payments_queue.full(): logger.debug("Payments queue is full. Wait a few minutes.") time.sleep(60 * 3) cycle = int( os.path.splitext( os.path.basename(payment_failed_report_file))[0]) # 2.3 read payments/failed/csv_report.csv file into a list of dictionaries batch = CsvPaymentFileParser().parse(payment_failed_report_file, cycle) nb_paid = len( list(filter(lambda f: f.paid == PaymentStatus.PAID, batch))) nb_done = len( list(filter(lambda f: f.paid == PaymentStatus.DONE, batch))) nb_injected = len( list(filter(lambda f: f.paid == PaymentStatus.INJECTED, batch))) nb_failed = len( list(filter(lambda f: f.paid == PaymentStatus.FAIL, batch))) logger.info( "Summary {} paid, {} done, {} injected, {} fail".format( nb_paid, nb_done, nb_injected, nb_failed)) if retry_injected: nb_converted = 0 for pl in batch: if pl.paid == PaymentStatus.INJECTED: pl.paid = PaymentStatus.FAIL nb_converted += 1 logger.debug( "Reward converted from %s to fail for cycle %s, address %s, amount %f, tz type %s", pl.paid, pl.cycle, pl.address, pl.amount, pl.type) if nb_converted: logger.info( "{} rewards converted from injected to fail.".format( nb_converted)) # 2.4 - Filter batch to only include those which failed. No need to mess with PAID/DONE batch = list(filter(lambda f: f.paid == PaymentStatus.FAIL, batch)) # 2.5 - Need to fetch current balance for addresses of any failed payments self.reward_api.update_current_balances(batch) # 2.6 - put records into payment_queue. payment_consumer will make payments self.payments_queue.put(PaymentBatch(self, cycle, batch)) # 2.7 - rename payments/failed/csv_report.csv to payments/failed/csv_report.csv.BUSY # mark the files as in use. we do not want it to be read again # BUSY file will be removed, if successful payment is done os.rename(payment_failed_report_file, payment_failed_report_file + BUSY_FILE)
def retry_failed_payments(self): logger.debug("retry_failed_payments started") # 1 - list csv files under payments/failed directory # absolute path of csv files found under payments_root/failed directory failed_payments_dir = get_failed_payments_dir(self.payments_root) payment_reports_failed = [ join(failed_payments_dir, x) for x in listdir(failed_payments_dir) if x.endswith(".csv") and int(x.split(".csv")[0]) >= self.initial_payment_cycle ] if payment_reports_failed: payment_reports_failed = sorted(payment_reports_failed, key=self.get_basename) logger.debug("Failed payment files found are: '{}'".format( ",".join(payment_reports_failed))) else: logger.info( "No failed payment files found under directory '{}' on or after cycle '{}'" .format(failed_payments_dir, self.initial_payment_cycle)) # 2- for each csv file with name csv_report.csv for payment_failed_report_file in payment_reports_failed: logger.info("Working on failed payment file {}".format( payment_failed_report_file)) # 2.1 - Read csv_report.csv under payments/failed and -if existing- under payments/done cycle = int( os.path.splitext( os.path.basename(payment_failed_report_file))[0]) batch = CsvPaymentFileParser().parse(payment_failed_report_file, cycle) payment_successful_report_file = payment_failed_report_file.replace( PAYMENT_FAILED_DIR, PAYMENT_DONE_DIR) if os.path.isfile(payment_successful_report_file): batch += CsvPaymentFileParser().parse( payment_successful_report_file, cycle) # 2.2 Translate batch into a list of dictionaries nb_paid = len( list(filter(lambda f: f.paid == PaymentStatus.PAID, batch))) nb_done = len( list(filter(lambda f: f.paid == PaymentStatus.DONE, batch))) nb_injected = len( list(filter(lambda f: f.paid == PaymentStatus.INJECTED, batch))) nb_failed = len( list(filter(lambda f: f.paid == PaymentStatus.FAIL, batch))) nb_avoided = len( list(filter(lambda f: f.paid == PaymentStatus.AVOIDED, batch))) logger.info( "Summary {} paid, {} done, {} injected, {} fail, {} avoided". format(nb_paid, nb_done, nb_injected, nb_failed, nb_avoided)) if self.retry_injected: self.convert_injected_to_fail(batch) # 2.3 - if queue is full, wait for sometime # make sure the queue is not full while self.payments_queue.full(): logger.debug( "Payments queue is full. Please wait three minutes.") sleep(60 * 3) # 2.5 - Need to fetch current balance for addresses of any failed payments self.reward_api.update_current_balances(batch) # 2.6 - put records into payment_queue. payment_consumer will make payments self.payments_queue.put( PaymentBatch(self.payment_producer, cycle, batch)) # 2.7 - rename payments/failed/csv_report.csv to payments/failed/csv_report.csv.BUSY # mark the files as in use. we do not want it to be read again # BUSY file will be removed, if successful payment is done os.rename(payment_failed_report_file, payment_failed_report_file + BUSY_FILE) return