mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-21 07:04:19 +01:00
drain: adds __init__.py and cleanups flake8 nits
This commit is contained in:
committed by
Christian Decker
parent
b7abc6f188
commit
04254693ab
0
drain/__init__.py
Normal file
0
drain/__init__.py
Normal 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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
TIMEOUT=60
|
TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
def wait_for(success, timeout=TIMEOUT):
|
def wait_for(success, timeout=TIMEOUT):
|
||||||
|
|||||||
Reference in New Issue
Block a user