drain: adds __init__.py and cleanups flake8 nits

This commit is contained in:
Michael Schmoock
2020-11-30 10:02:17 +01:00
committed by Christian Decker
parent b7abc6f188
commit 04254693ab
4 changed files with 37 additions and 35 deletions

0
drain/__init__.py Normal file
View File

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pyln.client import Plugin, Millisatoshi, RpcError from pyln.client import Plugin, Millisatoshi, RpcError
from utils import * from utils import get_ours, wait_ours
import re import re
import time import time
import uuid import uuid
@@ -19,7 +19,7 @@ HTLC_FEE_EST = Millisatoshi('3000sat')
HTLC_FEE_PAT = re.compile("^.* HTLC fee: ([0-9]+sat).*$") HTLC_FEE_PAT = re.compile("^.* HTLC fee: ([0-9]+sat).*$")
def setup_routing_fees(plugin, payload, route, amount, substractfees: bool=False): def setup_routing_fees(plugin, payload, route, amount, substractfees: bool = False):
delay = int(plugin.get_option('cltv-final')) delay = int(plugin.get_option('cltv-final'))
amount_iter = amount amount_iter = amount
@@ -66,9 +66,9 @@ def get_channel(plugin, payload, peer_id, scid=None):
try: try:
channel = next(c for c in peer['channels'] if 'short_channel_id' in c and c['short_channel_id'] == scid) channel = next(c for c in peer['channels'] if 'short_channel_id' in c and c['short_channel_id'] == scid)
except StopIteration: except StopIteration:
raise RpcError(payload['command'], payload, {'message': 'Cannot find channel %s for peer %s' % (scid, peer_id) }) raise RpcError(payload['command'], payload, {'message': 'Cannot find channel %s for peer %s' % (scid, peer_id)})
if channel['state'] != "CHANNELD_NORMAL": if channel['state'] != "CHANNELD_NORMAL":
raise RpcError(payload['command'], payload, {'message': 'Channel %s not in state CHANNELD_NORMAL, but: %s' % (scid, channel['state']) }) raise RpcError(payload['command'], payload, {'message': 'Channel %s not in state CHANNELD_NORMAL, but: %s' % (scid, channel['state'])})
if not peer['connected']: if not peer['connected']:
raise RpcError(payload['command'], payload, {'message': 'Channel %s peer is not connected.' % scid}) raise RpcError(payload['command'], payload, {'message': 'Channel %s peer is not connected.' % scid})
return channel return channel
@@ -152,16 +152,16 @@ def test_or_set_chunks(plugin, payload):
# get all spendable/receivables for our channels # get all spendable/receivables for our channels
channels = {} channels = {}
for channel in plugin.rpc.listchannels(source = payload['my_id']).get('channels'): for channel in plugin.rpc.listchannels(source=payload['my_id']).get('channels'):
if channel['short_channel_id'] == scid: if channel['short_channel_id'] == scid:
continue continue
try: try:
spend, recv = spendable_from_scid(plugin, payload, channel['short_channel_id'], True) spend, recv = spendable_from_scid(plugin, payload, channel['short_channel_id'], True)
except RpcError as e: except RpcError:
continue continue
channels[channel['short_channel_id']] = { channels[channel['short_channel_id']] = {
'spendable' : spend, 'spendable': spend,
'receivable' : recv, 'receivable': recv,
} }
if len(channels) == 0: if len(channels) == 0:
raise RpcError(payload['command'], payload, {'message': 'Not enough usable channels to perform cyclic routing.'}) raise RpcError(payload['command'], payload, {'message': 'Not enough usable channels to perform cyclic routing.'})
@@ -232,13 +232,13 @@ def try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable_before):
my_id = payload['my_id'] my_id = payload['my_id']
label = payload['command'] + "-" + str(uuid.uuid4()) label = payload['command'] + "-" + str(uuid.uuid4())
payload['labels'] += [label] payload['labels'] += [label]
description = "%s %s %s%s [%d/%d]" % (payload['command'], payload['scid'], payload['percentage'], '%', chunk+1, payload['chunks']) description = "%s %s %s%s [%d/%d]" % (payload['command'], payload['scid'], payload['percentage'], '%', chunk + 1, payload['chunks'])
invoice = plugin.rpc.invoice("any", label, description, payload['retry_for'] + 60) invoice = plugin.rpc.invoice("any", label, description, payload['retry_for'] + 60)
payment_hash = invoice['payment_hash'] payment_hash = invoice['payment_hash']
plugin.log("Invoice payment_hash: %s" % payment_hash) plugin.log("Invoice payment_hash: %s" % payment_hash)
# exclude selected channel to prevent unwanted shortcuts # exclude selected channel to prevent unwanted shortcuts
excludes = [payload['scid']+'/0', payload['scid']+'/1'] excludes = [payload['scid'] + '/0', payload['scid'] + '/1']
mychannels = plugin.rpc.listchannels(source=my_id).get('channels') mychannels = plugin.rpc.listchannels(source=my_id).get('channels')
# exclude local channels known to have too little capacity. # exclude local channels known to have too little capacity.
# getroute currently does not do this. # getroute currently does not do this.
@@ -247,23 +247,23 @@ def try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable_before):
continue # already added few lines above continue # already added few lines above
spend, recv = spendable_from_scid(plugin, payload, channel['short_channel_id']) spend, recv = spendable_from_scid(plugin, payload, channel['short_channel_id'])
if payload['command'] == 'drain' and recv < amount: if payload['command'] == 'drain' and recv < amount:
excludes += [channel['short_channel_id']+'/0', channel['short_channel_id']+'/1'] excludes += [channel['short_channel_id'] + '/0', channel['short_channel_id'] + '/1']
if payload['command'] == 'fill' and spend < amount: if payload['command'] == 'fill' and spend < amount:
excludes += [channel['short_channel_id']+'/0', channel['short_channel_id']+'/1'] excludes += [channel['short_channel_id'] + '/0', channel['short_channel_id'] + '/1']
while int(time.time()) - start_ts < payload['retry_for']: while int(time.time()) - start_ts < payload['retry_for']:
if payload['command'] == 'drain': if payload['command'] == 'drain':
r = plugin.rpc.getroute(my_id, amount, riskfactor=0, r = plugin.rpc.getroute(my_id, amount, riskfactor=0,
cltv=9, fromid=peer_id, fuzzpercent=0, exclude=excludes) cltv=9, fromid=peer_id, fuzzpercent=0, exclude=excludes)
route_out = {'id': peer_id, 'channel': payload['scid'], 'direction': int(my_id >= peer_id)} route_out = {'id': peer_id, 'channel': payload['scid'], 'direction': int(my_id >= peer_id)}
route = [route_out] + r['route'] route = [route_out] + r['route']
setup_routing_fees(plugin, payload, route, amount, True) setup_routing_fees(plugin, payload, route, amount, True)
if payload['command'] == 'fill': if payload['command'] == 'fill':
r = plugin.rpc.getroute(peer_id, amount, riskfactor=0, r = plugin.rpc.getroute(peer_id, amount, riskfactor=0,
cltv=9, fromid=my_id, fuzzpercent=0, exclude=excludes) cltv=9, fromid=my_id, fuzzpercent=0, exclude=excludes)
route_in = {'id': my_id, 'channel': payload['scid'], 'direction': int(peer_id >= my_id)} route_in = {'id': my_id, 'channel': payload['scid'], 'direction': int(peer_id >= my_id)}
route = r['route'] + [route_in] route = r['route'] + [route_in]
setup_routing_fees(plugin, payload, route, amount , False) setup_routing_fees(plugin, payload, route, amount, False)
fees = route[0]['amount_msat'] - route[-1]['amount_msat'] fees = route[0]['amount_msat'] - route[-1]['amount_msat']
@@ -276,7 +276,7 @@ def try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable_before):
excludes += [worst_channel_id + '/0', worst_channel_id + '/1'] excludes += [worst_channel_id + '/0', worst_channel_id + '/1']
continue continue
plugin.log("[%d/%d] Sending over %d hops to %s %s using %s fees" % (chunk+1, payload['chunks'], len(route), payload['command'], amount, fees), 'debug') plugin.log("[%d/%d] Sending over %d hops to %s %s using %s fees" % (chunk + 1, payload['chunks'], len(route), payload['command'], amount, fees), 'debug')
for r in route: for r in route:
plugin.log(" - %s %14s %s" % (r['id'], r['channel'], r['amount_msat']), 'debug') plugin.log(" - %s %14s %s" % (r['id'], r['channel'], r['amount_msat']), 'debug')
@@ -285,7 +285,7 @@ def try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable_before):
plugin.rpc.sendpay(route, payment_hash, label) plugin.rpc.sendpay(route, payment_hash, label)
result = plugin.rpc.waitsendpay(payment_hash, payload['retry_for'] + start_ts - int(time.time())) result = plugin.rpc.waitsendpay(payment_hash, payload['retry_for'] + start_ts - int(time.time()))
if result.get('status') == 'complete': if result.get('status') == 'complete':
payload['success_msg'] += ["%dmsat sent over %d hops to %s %dmsat [%d/%d]" % (amount + fees, len(route), payload['command'], amount, chunk+1, payload['chunks'])] payload['success_msg'] += ["%dmsat sent over %d hops to %s %dmsat [%d/%d]" % (amount + fees, len(route), payload['command'], amount, chunk + 1, payload['chunks'])]
# we need to wait for HTLC to resolve, so remaining amounts # we need to wait for HTLC to resolve, so remaining amounts
# can be calculated correctly for the next chunk # can be calculated correctly for the next chunk
wait_ours(plugin, payload['scid'], ours) wait_ours(plugin, payload['scid'], ours)
@@ -314,7 +314,7 @@ def try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable_before):
def read_params(command: str, scid: str, percentage: float, def read_params(command: str, scid: str, percentage: float,
chunks: int, maxfeepercent: float, retry_for: int, exemptfee: Millisatoshi): chunks: int, maxfeepercent: float, retry_for: int, exemptfee: Millisatoshi):
# check parameters # check parameters
if command != 'drain' and command != 'fill' and command != 'setbalance': if command != 'drain' and command != 'fill' and command != 'setbalance':
@@ -327,15 +327,15 @@ def read_params(command: str, scid: str, percentage: float,
# forge operation payload # forge operation payload
payload = { payload = {
"command" : command, "command": command,
"scid": scid, "scid": scid,
"percentage": percentage, "percentage": percentage,
"chunks": chunks, "chunks": chunks,
"maxfeepercent": maxfeepercent, "maxfeepercent": maxfeepercent,
"retry_for": retry_for, "retry_for": retry_for,
"exemptfee": exemptfee, "exemptfee": exemptfee,
"labels" : [], "labels": [],
"success_msg" : [], "success_msg": [],
} }
# cache some often required data # cache some often required data
@@ -396,7 +396,7 @@ def execute(payload: dict):
if amount < htlc_fee: if amount < htlc_fee:
raise RpcError(payload['command'], payload, {'message': 'channel too low to cover fees'}) raise RpcError(payload['command'], payload, {'message': 'channel too low to cover fees'})
amount -= htlc_fee amount -= htlc_fee
plugin.log("Trying... chunk:%s/%s spendable:%s receivable:%s htlc_fee:%s => amount:%s" % (chunk+1, payload['chunks'], spendable, receivable, htlc_fee, amount)) plugin.log("Trying... chunk:%s/%s spendable:%s receivable:%s htlc_fee:%s => amount:%s" % (chunk + 1, payload['chunks'], spendable, receivable, htlc_fee, amount))
try: try:
result = try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable) result = try_for_htlc_fee(plugin, payload, peer_id, amount, chunk, spendable)
@@ -425,8 +425,8 @@ def execute(payload: dict):
@plugin.method("drain") @plugin.method("drain")
def drain(plugin, scid: str, percentage: float=100, chunks: int=0, maxfeepercent: float=0.5, def drain(plugin, scid: str, percentage: float = 100, chunks: int = 0, maxfeepercent: float = 0.5,
retry_for: int=60, exemptfee: Millisatoshi=Millisatoshi(5000)): retry_for: int = 60, exemptfee: Millisatoshi = Millisatoshi(5000)):
"""Draining channel liquidity with circular payments. """Draining channel liquidity with circular payments.
Percentage defaults to 100, resulting in an empty channel. Percentage defaults to 100, resulting in an empty channel.
@@ -438,8 +438,8 @@ def drain(plugin, scid: str, percentage: float=100, chunks: int=0, maxfeepercent
@plugin.method("fill") @plugin.method("fill")
def fill(plugin, scid: str, percentage: float=100, chunks: int=0, maxfeepercent: float=0.5, def fill(plugin, scid: str, percentage: float = 100, chunks: int = 0, maxfeepercent: float = 0.5,
retry_for: int=60, exemptfee: Millisatoshi=Millisatoshi(5000)): retry_for: int = 60, exemptfee: Millisatoshi = Millisatoshi(5000)):
"""Filling channel liquidity with circular payments. """Filling channel liquidity with circular payments.
Percentage defaults to 100, resulting in a full channel. Percentage defaults to 100, resulting in a full channel.
@@ -451,8 +451,8 @@ def fill(plugin, scid: str, percentage: float=100, chunks: int=0, maxfeepercent:
@plugin.method("setbalance") @plugin.method("setbalance")
def setbalance(plugin, scid: str, percentage: float=50, chunks: int=0, maxfeepercent: float=0.5, def setbalance(plugin, scid: str, percentage: float = 50, chunks: int = 0, maxfeepercent: float = 0.5,
retry_for: int=60, exemptfee: Millisatoshi=Millisatoshi(5000)): retry_for: int = 60, exemptfee: Millisatoshi = Millisatoshi(5000)):
"""Brings a channels own liquidity to X percent using circular payments. """Brings a channels own liquidity to X percent using circular payments.
Percentage defaults to 50, resulting in a balanced channel. Percentage defaults to 50, resulting in a balanced channel.

View File

@@ -1,14 +1,16 @@
from pyln.testing.fixtures import * from pyln.testing.fixtures import * # noqa: F401,F403
from pyln.testing.utils import DEVELOPER from pyln.testing.utils import DEVELOPER
from pyln.client import RpcError, Millisatoshi from pyln.client import RpcError
from utils import * from .utils import get_ours, get_theirs, wait_ours, wait_for_all_htlcs
import os import os
import unittest import unittest
import pytest
pluginopt = {'plugin': os.path.join(os.path.dirname(__file__), "drain.py")} pluginopt = {'plugin': os.path.join(os.path.dirname(__file__), "drain.py")}
EXPERIMENTAL_FEATURES = int(os.environ.get("EXPERIMENTAL_FEATURES", "0")) EXPERIMENTAL_FEATURES = int(os.environ.get("EXPERIMENTAL_FEATURES", "0"))
@unittest.skipIf(not DEVELOPER, "slow gossip, needs DEVELOPER=1") @unittest.skipIf(not DEVELOPER, "slow gossip, needs DEVELOPER=1")
def test_drain_and_refill(node_factory, bitcoind): def test_drain_and_refill(node_factory, bitcoind):
# Scenario: first drain then refill # Scenario: first drain then refill
@@ -37,7 +39,7 @@ def test_drain_and_refill(node_factory, bitcoind):
# wait for each others gossip # wait for each others gossip
bitcoind.generate_block(6) bitcoind.generate_block(6)
for n in nodes: for n in nodes:
for scid in [scid12,scid23,scid34,scid41]: for scid in [scid12, scid23, scid34, scid41]:
n.wait_channel_active(scid) n.wait_channel_active(scid)
# do some draining and filling # do some draining and filling
@@ -87,7 +89,7 @@ def test_fill_and_drain(node_factory, bitcoind):
# wait for each others gossip # wait for each others gossip
bitcoind.generate_block(6) bitcoind.generate_block(6)
for n in nodes: for n in nodes:
for scid in [scid12,scid23,scid34,scid41]: for scid in [scid12, scid23, scid34, scid41]:
n.wait_channel_active(scid) n.wait_channel_active(scid)
# for l2 to fill scid12, it needs to send on scid23, where its funder # for l2 to fill scid12, it needs to send on scid23, where its funder
@@ -128,7 +130,7 @@ def test_setbalance(node_factory, bitcoind):
# wait for each others gossip # wait for each others gossip
bitcoind.generate_block(6) bitcoind.generate_block(6)
for n in nodes: for n in nodes:
for scid in [scid12,scid23,scid34,scid41]: for scid in [scid12, scid23, scid34, scid41]:
n.wait_channel_active(scid) n.wait_channel_active(scid)
# test auto 50/50 balancing # test auto 50/50 balancing

View File

@@ -1,6 +1,6 @@
import time import time
TIMEOUT=60 TIMEOUT = 60
def wait_for(success, timeout=TIMEOUT): def wait_for(success, timeout=TIMEOUT):