From 79ea6c9aa116f785365648c5948b140b95f952de Mon Sep 17 00:00:00 2001 From: Michael Schmoock Date: Wed, 15 Mar 2023 21:29:50 +0100 Subject: [PATCH] feeadjuster: set base fee for median strategy --- feeadjuster/feeadjuster.py | 21 ++++++++------ feeadjuster/test_feeadjuster.py | 51 ++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/feeadjuster/feeadjuster.py b/feeadjuster/feeadjuster.py index 3700d25..5864690 100755 --- a/feeadjuster/feeadjuster.py +++ b/feeadjuster/feeadjuster.py @@ -120,8 +120,11 @@ def get_fees_median(plugin: Plugin, scid: str): and ch['source'] != plugin.our_node_id] if len(channels_to_peer) == 0: return None - fees_ppm = [ch['fee_per_millionth'] for ch in channels_to_peer] - return {"base": plugin.adj_basefee, "ppm": statistics.median(fees_ppm) * plugin.median_multiplier} + # fees > ~5000 (base and ppm) are currently about top 2% of network fee extremists + fees_ppm = [ch['fee_per_millionth'] for ch in channels_to_peer if 0 < ch['fee_per_millionth'] < 5000] + fees_base = [ch['base_fee_millisatoshi'] for ch in channels_to_peer if 0 < ch['base_fee_millisatoshi'] < 5000] + return {"base": statistics.median(fees_base) * plugin.median_multiplier, + "ppm": statistics.median(fees_ppm) * plugin.median_multiplier} def setchannelfee(plugin: Plugin, scid: str, base: int, ppm: int, min_htlc: int = None, max_htlc: int = None): @@ -162,19 +165,19 @@ def maybe_adjust_fees(plugin: Plugin, scids: list): our = plugin.adj_balances[scid]["our"] total = plugin.adj_balances[scid]["total"] percentage = our / total - base = plugin.adj_basefee - ppm = plugin.adj_ppmfee + base = int(plugin.adj_basefee) + ppm = int(plugin.adj_ppmfee) # select ideal values per channel fees = plugin.fee_strategy(plugin, scid) if fees is not None: - base = fees['base'] - ppm = fees['ppm'] + base = int(fees['base']) + ppm = int(fees['ppm']) # reset to normal fees if imbalance is not high enough if (percentage > plugin.imbalance and percentage < 1 - plugin.imbalance): if setchannelfee(plugin, scid, base, ppm): - plugin.log(f"Set default fees as imbalance is too low: {scid}") + plugin.log(f"Set default fees as imbalance is too low for {scid}: ppm {ppm} base {base}msat") plugin.adj_balances[scid]["last_liquidity"] = our channels_adjusted += 1 continue @@ -189,8 +192,8 @@ def maybe_adjust_fees(plugin: Plugin, scids: list): max_htlc = int(total * math.ceil(plugin.max_htlc_steps * percentage) / plugin.max_htlc_steps) else: max_htlc = None - if setchannelfee(plugin, scid, int(base), int(ppm * ratio), None, max_htlc): - plugin.log(f"Adjusted fees of {scid} with a ratio of {ratio}, set max_htlc to {max_htlc}") + if setchannelfee(plugin, scid, base, int(ppm * ratio), None, max_htlc): + plugin.log(f"Adjusted fees of {scid} with a ratio of {ratio}: ppm {int(ppm * ratio)} base {base}msat max_htlc {max_htlc}") plugin.adj_balances[scid]["last_liquidity"] = our channels_adjusted += 1 return channels_adjusted diff --git a/feeadjuster/test_feeadjuster.py b/feeadjuster/test_feeadjuster.py index 16b5cc3..a2b8346 100644 --- a/feeadjuster/test_feeadjuster.py +++ b/feeadjuster/test_feeadjuster.py @@ -189,8 +189,8 @@ def test_feeadjuster_imbalance(node_factory): amount = int(chan_total * 0.5) pay(l1, l3, amount) l2.daemon.wait_for_logs([ - f'Set default fees as imbalance is too low: {scid_A}', - f'Set default fees as imbalance is too low: {scid_B}' + f'Set default fees as imbalance is too low for {scid_A}', + f'Set default fees as imbalance is too low for {scid_B}' ]) wait_for_fees(l2, scids, default_fees[0]) @@ -211,8 +211,8 @@ def test_feeadjuster_imbalance(node_factory): # Bringing it back must cause default fees pay(l3, l1, amount) l2.daemon.wait_for_logs([ - f'Set default fees as imbalance is too low: {scid_A}', - f'Set default fees as imbalance is too low: {scid_B}' + f'Set default fees as imbalance is too low for {scid_A}', + f'Set default fees as imbalance is too low for {scid_B}' ]) wait_for_fees(l2, scids, default_fees[0]) @@ -290,3 +290,46 @@ def test_feeadjuster_big_enough_liquidity(node_factory): f"Adjusted fees.*{scid_B}" ]) wait_for_not_fees(l2, scids, default_fees[0]) + + +@unittest.skipIf(not DEVELOPER, "Too slow without fast gossip") +def test_feeadjuster_median(node_factory): + """ + A rather simple network: + + a b c + l1 <=======> l2 <=======> l3 <=======> l4 + + l2 will adjust its configuration-set base and proportional fees for + channels A and B as l1 and l3 exchange payments. + l4 is needed so l2 can make a median peers-of-peer calculation on l3. + """ + opts = { + "fee-base": 1337, + "fee-per-satoshi": 42, + } + l2_opts = { + "fee-base": 1000, + "fee-per-satoshi": 100, + "plugin": plugin_path, + "feeadjuster-deactivate-fuzz": None, + "feeadjuster-imbalance": 0.5, + "feeadjuster-feestrategy": "median" + } + l1, l2, l3, _ = node_factory.line_graph(4, opts=[opts, l2_opts, opts, opts], + wait_for_announce=True) + + scid_a = l2.rpc.listpeerchannels(l1.info["id"])["channels"][0]["short_channel_id"] + scid_b = l2.rpc.listpeerchannels(l3.info["id"])["channels"][0]["short_channel_id"] + + # we do a manual feeadjust + l2.rpc.feeadjust() + l2.daemon.wait_for_logs([ + f"Adjusted fees.*{scid_a}", + f"Adjusted fees.*{scid_b}" + ]) + + # since there is only l4 with channel c towards l3, l2 should take that value + chan_b = l2.rpc.listpeerchannels(l3.info['id'])['channels'][0] + assert chan_b['fee_base_msat'] == 1337 + assert chan_b['fee_proportional_millionths'] < 42 # we could do the actual ratio math, but meh