diff --git a/contrib/giantnode.py b/contrib/giantnode.py new file mode 100755 index 000000000..0c14eecb3 --- /dev/null +++ b/contrib/giantnode.py @@ -0,0 +1,183 @@ +#! /usr/bin/env python3 +# Script to create huge numbers of forwards/payments/etc for regtest nodes. + +# To initialize, use contrib/startup_regtest.sh: +# $ . contrib/startup_regtest.sh +# $ start_ln 3 +# $ fund_nodes + +import argparse +import random +import string +import sys +import time +import multiprocessing +from pyln.client import LightningRpc, RpcError + +parser = argparse.ArgumentParser( + description='Flood three funded startup_regtest nodes' +) +parser.add_argument('--fail-forward', action="store_true", + help='Create failed forward attempts', + default=False) +parser.add_argument('--fail-pay', action="store_true", + help='Allow some failed pay attempts (faster!)', + default=False) +parser.add_argument('--inv-runners', type=int, + help='How many invoice-generation processes to run at once', + default=1) +parser.add_argument('--pay-runners', type=int, + help='How many invoice-paying processes to run at once', + default=8) +parser.add_argument('--check-runners', type=int, + help='How many pay-checking processes to run at once', + default=1) +parser.add_argument('--allow-bookkeeper', action="store_true", + help="Don't stop if bookkeeper is running", + default=False) +parser.add_argument('num', type=int, nargs='?', + help='number to attempt', + default=1000000) + +args = parser.parse_args() + +nodes = (LightningRpc('/tmp/l1-regtest/regtest/lightning-rpc'), + LightningRpc('/tmp/l2-regtest/regtest/lightning-rpc'), + LightningRpc('/tmp/l3-regtest/regtest/lightning-rpc')) + +nodes = (LightningRpc('/tmp/l1-regtest/regtest/lightning-rpc'), + LightningRpc('/tmp/l2-regtest/regtest/lightning-rpc'), + LightningRpc('/tmp/l3-regtest/regtest/lightning-rpc')) + +# Convenient aliases +l1 = nodes[0] +l2 = nodes[1] +l3 = nodes[2] + +if not args.allow_bookkeeper: + if any([p['name'].endswith('bookkeeper') for p in l1.plugin_list()['plugins']]): + print(""" +Bookkeeper is running on l1, will slow things down! Run this: + +echo 'disable-plugin=bookkeeper' >> /tmp/l1-regtest/regtest/config +echo 'disable-plugin=bookkeeper' >> /tmp/l2-regtest/regtest/config +echo 'disable-plugin=bookkeeper' >> /tmp/l3-regtest/regtest/config +stop_ln +start_ln 3 +""") + sys.exit(1) + +route = l1.getroute(l3.getinfo()['id'], 1, 1)['route'] +if args.fail_forward: + route[1]['channel'] = '1x1x1' + + +def get_invs(inv_q, num, report_q): + """Runners feed invoices into the queue""" + prefix = ''.join(random.choice(string.ascii_lowercase) for _ in range(10)) + for i in range(num): + inv_q.put(l3.invoice(amount_msat=1, + label='{}-{}'.format(prefix, i), + description='giantnode')) + report_q.put('i') + + +def send_pay(inv_q, num, done_q, report_q): + """Runner to fetch invoices from queue and send""" + for i in range(num): + inv = inv_q.get() + l1.sendpay(route, + inv['payment_hash'], + payment_secret=inv['payment_secret']) + report_q.put('p') + done_q.put(inv) + + +def checker(num, inv_q, done_q, report_q): + """Runner which checks that invs didn't fail (TOO_MANY_HTLCS!), and requeues if they did!""" + for i in range(num): + inv = done_q.get() + try: + res = l1.waitsendpay(inv['payment_hash']) + except RpcError: + report_q.put('f') + if not args.fail_pay and not args.fail_forward: + inv_q.put(inv) + continue + assert res['status'] == 'complete' + report_q.put('w') + + +inv_q = multiprocessing.Queue() +done_q = multiprocessing.Queue() +report_q = multiprocessing.Queue() + +# In case it doesn't divide +extra_prod = (args.num % args.inv_runners,) + (0,) * (args.inv_runners - 1) +inv_producers = [multiprocessing.Process(target=get_invs, args=(inv_q, args.num // args.inv_runners + extra_prod[i], report_q)) for i in range(args.inv_runners)] +extra_cons = (args.num % args.pay_runners,) + (0,) * (args.pay_runners - 1) +inv_consumers = [multiprocessing.Process(target=send_pay, args=(inv_q, args.num // args.pay_runners + extra_cons[i], done_q, report_q)) for i in range(args.pay_runners)] + +extra_checks = (args.num % args.check_runners,) + (0,) * (args.check_runners - 1) +checkers = [multiprocessing.Process(target=checker, args=(args.num // args.check_runners + extra_checks[i], inv_q, done_q, report_q)) for i in range(args.check_runners)] + +prev = start = time.time() +for i in inv_producers + inv_consumers + checkers: + i.start() + +num_total = 0 +num_successes = 0 +num_invs = 0 +num_pays = 0 +num_retries = 0 +prev_total = 0 + + +def timefmt(number, time): + if time == 0: + return '?' + seconds = number / time + minutes = seconds / 60 + hours = minutes / 60 + if hours > 2: + return '{} hours'.format(int(hours)) + if minutes > 2: + return '{} minutes'.format(int(minutes)) + + return '{} seconds'.format(int(seconds)) + + +while num_total < args.num: + letter = report_q.get() + if letter == 'i': + num_invs += 1 + elif letter == 'p': + num_pays += 1 + elif letter == 'f': + num_retries += 1 + elif letter == 'w': + num_successes += 1 + else: + assert False + + if args.fail_pay or args.fail_forward: + num_total = num_pays + else: + num_total = num_successes + + now = time.time() + if now > prev + 10: + current_rate = (num_total - prev_total) / (now - prev) + prev = now + prev_total = num_total + total_rate = num_total / (now - start) + print("{}/{} complete {}/sec ({} invs, {} pays, {} retries) in {} seconds. {}-{} remaining." + .format(num_total, args.num, format(current_rate, ".2f"), + num_invs, num_pays, num_retries, int(now - start), + timefmt(args.num - num_total, total_rate), + timefmt(args.num - num_total, current_rate))) + +for i in inv_producers + inv_consumers + checkers: + i.join() + +print("done")